1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the syntax check for JavaScript. |
|
8 """ |
|
9 |
|
10 import multiprocessing |
|
11 import os |
|
12 import queue |
|
13 import sys |
|
14 |
|
15 |
|
16 def initService(): |
|
17 """ |
|
18 Initialize the service and return the entry point. |
|
19 |
|
20 @return the entry point for the background client (function) |
|
21 """ |
|
22 path = __file__ |
|
23 for _ in range(4): |
|
24 path = os.path.dirname(path) |
|
25 sys.path.insert(2, os.path.join(path, "ThirdParty", "Jasy")) |
|
26 return jsSyntaxCheck |
|
27 |
|
28 |
|
29 def initBatchService(): |
|
30 """ |
|
31 Initialize the batch service and return the entry point. |
|
32 |
|
33 @return the entry point for the background client (function) |
|
34 """ |
|
35 return jsSyntaxBatchCheck |
|
36 |
|
37 |
|
38 def jsSyntaxCheck(file, codestring): |
|
39 """ |
|
40 Function to check a Javascript source file for syntax errors. |
|
41 |
|
42 @param file source filename (string) |
|
43 @param codestring string containing the code to check (string) |
|
44 @return dictionary with the keys 'error' and 'warnings' which |
|
45 hold a list containing details about the error/ warnings |
|
46 (file name, line number, column, codestring (only at syntax |
|
47 errors), the message, a list with arguments for the message) |
|
48 """ |
|
49 return __jsSyntaxCheck(file, codestring) |
|
50 |
|
51 |
|
52 def jsSyntaxBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0): |
|
53 """ |
|
54 Module function to check syntax for a batch of files. |
|
55 |
|
56 @param argumentsList list of arguments tuples as given for jsSyntaxCheck |
|
57 @type list |
|
58 @param send reference to send function |
|
59 @type func |
|
60 @param fx registered service name |
|
61 @type str |
|
62 @param cancelled reference to function checking for a cancellation |
|
63 @type func |
|
64 @param maxProcesses number of processes to be used |
|
65 @type int |
|
66 """ |
|
67 if maxProcesses == 0: |
|
68 # determine based on CPU count |
|
69 try: |
|
70 NumberOfProcesses = multiprocessing.cpu_count() |
|
71 if NumberOfProcesses >= 1: |
|
72 NumberOfProcesses -= 1 |
|
73 except NotImplementedError: |
|
74 NumberOfProcesses = 1 |
|
75 else: |
|
76 NumberOfProcesses = maxProcesses |
|
77 |
|
78 # Create queues |
|
79 taskQueue = multiprocessing.Queue() |
|
80 doneQueue = multiprocessing.Queue() |
|
81 |
|
82 # Submit tasks (initially two times the number of processes) |
|
83 tasks = len(argumentsList) |
|
84 initialTasks = min(2 * NumberOfProcesses, tasks) |
|
85 for _ in range(initialTasks): |
|
86 taskQueue.put(argumentsList.pop(0)) |
|
87 |
|
88 # Start worker processes |
|
89 workers = [ |
|
90 multiprocessing.Process(target=workerTask, args=(taskQueue, doneQueue)) |
|
91 for _ in range(NumberOfProcesses) |
|
92 ] |
|
93 for worker in workers: |
|
94 worker.start() |
|
95 |
|
96 # Get and send results |
|
97 for _ in range(tasks): |
|
98 resultSent = False |
|
99 wasCancelled = False |
|
100 |
|
101 while not resultSent: |
|
102 try: |
|
103 # get result (waiting max. 3 seconds and send it to frontend |
|
104 filename, result = doneQueue.get() |
|
105 send(fx, filename, result) |
|
106 resultSent = True |
|
107 except queue.Empty: |
|
108 # ignore empty queue, just carry on |
|
109 if cancelled(): |
|
110 wasCancelled = True |
|
111 break |
|
112 |
|
113 if wasCancelled or cancelled(): |
|
114 # just exit the loop ignoring the results of queued tasks |
|
115 break |
|
116 |
|
117 if argumentsList: |
|
118 taskQueue.put(argumentsList.pop(0)) |
|
119 |
|
120 # Tell child processes to stop |
|
121 for _ in range(NumberOfProcesses): |
|
122 taskQueue.put("STOP") |
|
123 |
|
124 for worker in workers: |
|
125 worker.join() |
|
126 worker.close() |
|
127 |
|
128 taskQueue.close() |
|
129 doneQueue.close() |
|
130 |
|
131 |
|
132 def workerTask(inputQueue, outputQueue): |
|
133 """ |
|
134 Module function acting as the parallel worker for the syntax check. |
|
135 |
|
136 @param inputQueue input queue (multiprocessing.Queue) |
|
137 @param outputQueue output queue (multiprocessing.Queue) |
|
138 """ |
|
139 for filename, args in iter(inputQueue.get, "STOP"): |
|
140 source = args[0] |
|
141 result = __jsSyntaxCheck(filename, source) |
|
142 outputQueue.put((filename, result)) |
|
143 |
|
144 |
|
145 def __jsSyntaxCheck(file, codestring): |
|
146 """ |
|
147 Function to check a Javascript source file for syntax errors. |
|
148 |
|
149 @param file source filename (string) |
|
150 @param codestring string containing the code to check (string) |
|
151 @return dictionary with the keys 'error' and 'warnings' which |
|
152 hold a list containing details about the error/ warnings |
|
153 (file name, line number, column, codestring (only at syntax |
|
154 errors), the message, a list with arguments for the message) |
|
155 """ |
|
156 import jasy.script.parse.Parser as jsParser # __IGNORE_WARNING_I102__ |
|
157 import jasy.script.tokenize.Tokenizer as jsTokenizer # __IGNORE_WARNING_I102__ |
|
158 |
|
159 try: |
|
160 jsParser.parse(codestring, file) |
|
161 except (jsParser.SyntaxError, jsTokenizer.ParseError) as exc: |
|
162 details = exc.args[0] |
|
163 error, details = details.splitlines() |
|
164 fn, line = details.strip().rsplit(":", 1) |
|
165 error = error.split(":", 1)[1].strip() |
|
166 |
|
167 cline = min(len(codestring.splitlines()), int(line)) - 1 |
|
168 code = codestring.splitlines()[cline] |
|
169 return [{"error": (fn, int(line), 0, code, error)}] |
|
170 except IndexError: |
|
171 error = "Incomplete source file" |
|
172 splittedCode = codestring.splitlines() |
|
173 return [ |
|
174 { |
|
175 "error": ( |
|
176 file, |
|
177 len(splittedCode) + 1, |
|
178 len(splittedCode[-1]), |
|
179 splittedCode[-1], |
|
180 error, |
|
181 ) |
|
182 } |
|
183 ] |
|
184 |
|
185 return [{}] |
|