Syntax Checker:

Tue, 19 Nov 2019 18:53:58 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 19 Nov 2019 18:53:58 +0100
changeset 7335
07ed3d73bf58
parent 7334
be378821131e
child 7336
0095e231ddaf

Syntax Checker:
- added a syntax checker for JSON files
- added a syntax checker for YAML files

docs/changelog file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py file | annotate | diff | comparison | revisions
eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckService.py file | annotate | diff | comparison | revisions
eric6/Plugins/CheckerPlugins/SyntaxChecker/jsCheckSyntax.py file | annotate | diff | comparison | revisions
eric6/Plugins/CheckerPlugins/SyntaxChecker/jsonCheckSyntax.py file | annotate | diff | comparison | revisions
eric6/Plugins/CheckerPlugins/SyntaxChecker/yamlCheckSyntax.py file | annotate | diff | comparison | revisions
eric6/Plugins/PluginSyntaxChecker.py file | annotate | diff | comparison | revisions
eric6/QScintilla/Editor.py file | annotate | diff | comparison | revisions
eric6/Utilities/BackgroundService.py file | annotate | diff | comparison | revisions
--- a/docs/changelog	Thu Nov 14 19:39:07 2019 +0100
+++ b/docs/changelog	Tue Nov 19 18:53:58 2019 +0100
@@ -6,6 +6,9 @@
   -- added a menu entry to show the local and device time side-by-side
   -- added a PyBoard menu entry to flash a new firmware
   -- added a menu entry to open the firmware download page of a device
+- Syntax Checker
+  -- added a syntax checker for JSON files
+  -- added a syntax checker for YAML files
 - install script
   -- added the "--yes" flag to allow installation of all missing prerequisites
      with pip without asking
--- a/eric6.e4p	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6.e4p	Tue Nov 19 18:53:58 2019 +0100
@@ -319,10 +319,12 @@
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckerDialog.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/__init__.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/jsCheckSyntax.py</Source>
+    <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/jsonCheckSyntax.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/__init__.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/messages.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/translations.py</Source>
+    <Source>eric6/Plugins/CheckerPlugins/SyntaxChecker/yamlCheckSyntax.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/Tabnanny/Tabnanny.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/Tabnanny/TabnannyDialog.py</Source>
     <Source>eric6/Plugins/CheckerPlugins/Tabnanny/__init__.py</Source>
--- a/eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py	Tue Nov 19 18:53:58 2019 +0100
@@ -2,7 +2,6 @@
 
 # Copyright (c) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
 #
-# pylint: disable=C0103
 
 """
 Module implementing the syntax check for Python 2/3.
@@ -202,7 +201,7 @@
 
 def worker(inputQueue, outputQueue):
     """
-    Module function acting as the parallel worker for the style check.
+    Module function acting as the parallel worker for the syntax check.
     
     @param inputQueue input queue (multiprocessing.Queue)
     @param outputQueue output queue (multiprocessing.Queue)
--- a/eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckService.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheckService.py	Tue Nov 19 18:53:58 2019 +0100
@@ -240,6 +240,38 @@
                 self.__serviceError(self.tr("JavaScript batch check"), msg)
                 self.batchJobDone(fx, lang)
     
+    def serviceErrorYAML(self, fx, lang, fn, msg):
+        """
+        Public method handling service errors for YAML.
+        
+        @param fx service name (string)
+        @param lang language (string)
+        @param fn file name (string)
+        @param msg message text (string)
+        """
+        if fx in ['YAMLSyntax', 'batch_YAMLSyntax']:
+            if fx == 'YAMLSyntax':
+                self.__serviceError(fn, msg)
+            else:
+                self.__serviceError(self.tr("YAML batch check"), msg)
+                self.batchJobDone(fx, lang)
+    
+    def serviceErrorJSON(self, fx, lang, fn, msg):
+        """
+        Public method handling service errors for JSON.
+        
+        @param fx service name (string)
+        @param lang language (string)
+        @param fn file name (string)
+        @param msg message text (string)
+        """
+        if fx in ['JSONSyntax', 'batch_JSONSyntax']:
+            if fx == 'JSONSyntax':
+                self.__serviceError(fn, msg)
+            else:
+                self.__serviceError(self.tr("JSON batch check"), msg)
+                self.batchJobDone(fx, lang)
+    
     def batchJobDone(self, fx, lang):
         """
         Public slot handling the completion of a batch job.
@@ -247,9 +279,13 @@
         @param fx service name (string)
         @param lang language (string)
         """
-        if fx in ['Python2Syntax', 'batch_Python2Syntax',
-                  'Python3Syntax', 'batch_Python3Syntax',
-                  'JavaScriptSyntax', 'batch_JavaScriptSyntax']:
+        if fx in [
+            'Python2Syntax', 'batch_Python2Syntax',
+            'Python3Syntax', 'batch_Python3Syntax',
+            'JavaScriptSyntax', 'batch_JavaScriptSyntax',
+            'YAMLSyntax', 'batch_YAMLSyntax',
+            'JSONSyntax', 'batch_JSONSyntax',
+        ]:
             if lang in self.queuedBatches:
                 self.queuedBatches.remove(lang)
             # prevent sending the signal multiple times
--- a/eric6/Plugins/CheckerPlugins/SyntaxChecker/jsCheckSyntax.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/jsCheckSyntax.py	Tue Nov 19 18:53:58 2019 +0100
@@ -2,19 +2,12 @@
 
 # Copyright (c) 2014 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
 #
-# pylint: disable=C0103
 
 """
-Module implementing the syntax check for Python 2/3.
+Module implementing the syntax check for JavaScript.
 """
 
-from __future__ import unicode_literals
-
-try:  # Only for Py2
-    import Queue as queue
-except ImportError:
-    import queue
-
+import queue
 import os
 import sys
 import multiprocessing
@@ -144,7 +137,7 @@
 
 def worker(inputQueue, outputQueue):
     """
-    Module function acting as the parallel worker for the style check.
+    Module function acting as the parallel worker for the syntax check.
     
     @param inputQueue input queue (multiprocessing.Queue)
     @param outputQueue output queue (multiprocessing.Queue)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/jsonCheckSyntax.py	Tue Nov 19 18:53:58 2019 +0100
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the syntax check for JSON.
+"""
+
+import queue
+import multiprocessing
+
+
+def initService():
+    """
+    Initialize the service and return the entry point.
+    
+    @return the entry point for the background client
+    @rtype func
+    """
+    return jsonSyntaxCheck
+
+
+def initBatchService():
+    """
+    Initialize the batch service and return the entry point.
+    
+    @return the entry point for the background client
+    @rtype func
+    """
+    return jsonSyntaxBatchCheck
+
+
+def normalizeCode(codestring):
+    """
+    Function to normalize the given code.
+    
+    @param codestring code to be normalized
+    @type str
+    @return normalized code
+    @rtype str
+    """
+    codestring = codestring.replace("\r\n", "\n").replace("\r", "\n")
+
+    if codestring and codestring[-1] != '\n':
+        codestring = codestring + '\n'
+    
+    return codestring
+
+
+def jsonSyntaxCheck(file, codestring):
+    """
+    Function to check a JSON source file for syntax errors.
+    
+    @param file source filename
+    @type str
+    @param codestring string containing the code to check
+    @type str
+    @return dictionary with the keys 'error' and 'warnings' which
+            hold a list containing details about the error/ warnings
+            (file name, line number, column, codestring (only at syntax
+            errors), the message, a list with arguments for the message)
+    @rtype dict
+    """
+    return __jsonSyntaxCheck(file, codestring)
+
+
+def jsonSyntaxBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0):
+    """
+    Module function to check syntax for a batch of files.
+    
+    @param argumentsList list of arguments tuples as given for yamlSyntaxCheck
+    @type list
+    @param send reference to send function
+    @type func
+    @param fx registered service name
+    @type str
+    @param cancelled reference to function checking for a cancellation
+    @type func
+    @param maxProcesses number of processes to be used
+    @type int
+    """
+    if maxProcesses == 0:
+        # determine based on CPU count
+        try:
+            NumberOfProcesses = multiprocessing.cpu_count()
+            if NumberOfProcesses >= 1:
+                NumberOfProcesses -= 1
+        except NotImplementedError:
+            NumberOfProcesses = 1
+    else:
+        NumberOfProcesses = maxProcesses
+
+    # Create queues
+    taskQueue = multiprocessing.Queue()
+    doneQueue = multiprocessing.Queue()
+
+    # Submit tasks (initially two time number of processes
+    initialTasks = 2 * NumberOfProcesses
+    for task in argumentsList[:initialTasks]:
+        taskQueue.put(task)
+
+    # Start worker processes
+    for _ in range(NumberOfProcesses):
+        multiprocessing.Process(
+            target=worker, args=(taskQueue, doneQueue)
+        ).start()
+
+    # Get and send results
+    endIndex = len(argumentsList) - initialTasks
+    for i in range(len(argumentsList)):
+        resultSent = False
+        wasCancelled = False
+        
+        while not resultSent:
+            try:
+                # get result (waiting max. 3 seconds and send it to frontend
+                filename, result = doneQueue.get()
+                send(fx, filename, result)
+                resultSent = True
+            except queue.Empty:
+                # ignore empty queue, just carry on
+                if cancelled():
+                    wasCancelled = True
+                    break
+        
+        if wasCancelled or cancelled():
+            # just exit the loop ignoring the results of queued tasks
+            break
+        
+        if i < endIndex:
+            taskQueue.put(argumentsList[i + initialTasks])
+
+    # Tell child processes to stop
+    for _ in range(NumberOfProcesses):
+        taskQueue.put('STOP')
+
+
+def worker(inputQueue, outputQueue):
+    """
+    Module function acting as the parallel worker for the syntax check.
+    
+    @param inputQueue input queue
+    @type multiprocessing.Queue
+    @param outputQueue output queue
+    @type multiprocessing.Queue
+    """
+    for filename, args in iter(inputQueue.get, 'STOP'):
+        source = args[0]
+        result = __jsonSyntaxCheck(filename, source)
+        outputQueue.put((filename, result))
+
+
+def __jsonSyntaxCheck(file, codestring):
+    """
+    Function to check a YAML source file for syntax errors.
+    
+    @param file source filename
+    @type str
+    @param codestring string containing the code to check
+    @type str
+    @return dictionary with the keys 'error' and 'warnings' which
+            hold a list containing details about the error/ warnings
+            (file name, line number, column, codestring (only at syntax
+            errors), the message, a list with arguments for the message)
+    @rtype dict
+    """
+    import json
+    
+    codestring = normalizeCode(codestring)
+    
+    try:
+        json.loads(codestring)
+    except json.JSONDecodeError as exc:
+        line = exc.lineno
+        column = exc.colno
+        error = exc.msg
+        
+        cline = min(len(codestring.splitlines()), int(line)) - 1
+        code = codestring.splitlines()[cline]
+        return [{'error': (file, line, column, code, error)}]
+    
+    return [{}]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/yamlCheckSyntax.py	Tue Nov 19 18:53:58 2019 +0100
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the syntax check for YAML.
+"""
+
+import queue
+import multiprocessing
+
+
+def initService():
+    """
+    Initialize the service and return the entry point.
+    
+    @return the entry point for the background client
+    @rtype func
+    """
+    return yamlSyntaxCheck
+
+
+def initBatchService():
+    """
+    Initialize the batch service and return the entry point.
+    
+    @return the entry point for the background client
+    @rtype func
+    """
+    return yamlSyntaxBatchCheck
+
+
+def normalizeCode(codestring):
+    """
+    Function to normalize the given code.
+    
+    @param codestring code to be normalized
+    @type str
+    @return normalized code
+    @rtype str
+    """
+    codestring = codestring.replace("\r\n", "\n").replace("\r", "\n")
+
+    if codestring and codestring[-1] != '\n':
+        codestring = codestring + '\n'
+    
+    return codestring
+
+
+def yamlSyntaxCheck(file, codestring):
+    """
+    Function to check a YAML source file for syntax errors.
+    
+    @param file source filename
+    @type str
+    @param codestring string containing the code to check
+    @type str
+    @return dictionary with the keys 'error' and 'warnings' which
+            hold a list containing details about the error/ warnings
+            (file name, line number, column, codestring (only at syntax
+            errors), the message, a list with arguments for the message)
+    @rtype dict
+    """
+    return __yamlSyntaxCheck(file, codestring)
+
+
+def yamlSyntaxBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0):
+    """
+    Module function to check syntax for a batch of files.
+    
+    @param argumentsList list of arguments tuples as given for yamlSyntaxCheck
+    @type list
+    @param send reference to send function
+    @type func
+    @param fx registered service name
+    @type str
+    @param cancelled reference to function checking for a cancellation
+    @type func
+    @param maxProcesses number of processes to be used
+    @type int
+    """
+    if maxProcesses == 0:
+        # determine based on CPU count
+        try:
+            NumberOfProcesses = multiprocessing.cpu_count()
+            if NumberOfProcesses >= 1:
+                NumberOfProcesses -= 1
+        except NotImplementedError:
+            NumberOfProcesses = 1
+    else:
+        NumberOfProcesses = maxProcesses
+
+    # Create queues
+    taskQueue = multiprocessing.Queue()
+    doneQueue = multiprocessing.Queue()
+
+    # Submit tasks (initially two time number of processes
+    initialTasks = 2 * NumberOfProcesses
+    for task in argumentsList[:initialTasks]:
+        taskQueue.put(task)
+
+    # Start worker processes
+    for _ in range(NumberOfProcesses):
+        multiprocessing.Process(
+            target=worker, args=(taskQueue, doneQueue)
+        ).start()
+
+    # Get and send results
+    endIndex = len(argumentsList) - initialTasks
+    for i in range(len(argumentsList)):
+        resultSent = False
+        wasCancelled = False
+        
+        while not resultSent:
+            try:
+                # get result (waiting max. 3 seconds and send it to frontend
+                filename, result = doneQueue.get()
+                send(fx, filename, result)
+                resultSent = True
+            except queue.Empty:
+                # ignore empty queue, just carry on
+                if cancelled():
+                    wasCancelled = True
+                    break
+        
+        if wasCancelled or cancelled():
+            # just exit the loop ignoring the results of queued tasks
+            break
+        
+        if i < endIndex:
+            taskQueue.put(argumentsList[i + initialTasks])
+
+    # Tell child processes to stop
+    for _ in range(NumberOfProcesses):
+        taskQueue.put('STOP')
+
+
+def worker(inputQueue, outputQueue):
+    """
+    Module function acting as the parallel worker for the syntax check.
+    
+    @param inputQueue input queue
+    @type multiprocessing.Queue
+    @param outputQueue output queue
+    @type multiprocessing.Queue
+    """
+    for filename, args in iter(inputQueue.get, 'STOP'):
+        source = args[0]
+        result = __yamlSyntaxCheck(filename, source)
+        outputQueue.put((filename, result))
+
+
+def __yamlSyntaxCheck(file, codestring):
+    """
+    Function to check a YAML source file for syntax errors.
+    
+    @param file source filename
+    @type str
+    @param codestring string containing the code to check
+    @type str
+    @return dictionary with the keys 'error' and 'warnings' which
+            hold a list containing details about the error/ warnings
+            (file name, line number, column, codestring (only at syntax
+            errors), the message, a list with arguments for the message)
+    @rtype dict
+    """
+    try:
+        from yaml import safe_load_all, MarkedYAMLError
+    except ImportError:
+        error = "pyyaml not available. Install it via the PyPI interface."
+        return [{'error': (file, 0, 0, '', error)}]
+    
+    codestring = normalizeCode(codestring)
+    
+    try:
+        for _obj in safe_load_all(codestring):
+            # do nothing with it, just to get parse errors
+            pass
+    except MarkedYAMLError as exc:
+        if exc.problem_mark:
+            line = exc.problem_mark.line + 1
+            column = exc.problem_mark.column
+        else:
+            line, column = 0, 0
+        error = exc.problem
+        cline = min(len(codestring.splitlines()), int(line)) - 1
+        code = codestring.splitlines()[cline]
+        return [{'error': (file, line, column, code, error)}]
+    
+    return [{}]
--- a/eric6/Plugins/PluginSyntaxChecker.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/Plugins/PluginSyntaxChecker.py	Tue Nov 19 18:53:58 2019 +0100
@@ -88,6 +88,26 @@
             lambda fn, problems:
                 self.syntaxCheckService.syntaxChecked.emit(fn, problems),
             self.syntaxCheckService.serviceErrorJavaScript)
+        
+        # YAML syntax check via Python3
+        self.syntaxCheckService.addLanguage(
+            'YAML', 'Python3', path,
+            'yamlCheckSyntax',
+            lambda: [],  # No options
+            lambda: ['.yml', '.yaml'],
+            lambda fn, problems:
+                self.syntaxCheckService.syntaxChecked.emit(fn, problems),
+            self.syntaxCheckService.serviceErrorYAML)
+        
+        # JSON syntax check via Python3
+        self.syntaxCheckService.addLanguage(
+            'JSON', 'Python3', path,
+            'jsonCheckSyntax',
+            lambda: [],  # No options
+            lambda: ['.json'],
+            lambda fn, problems:
+                self.syntaxCheckService.syntaxChecked.emit(fn, problems),
+            self.syntaxCheckService.serviceErrorJSON)
 
     def __initialize(self):
         """
--- a/eric6/QScintilla/Editor.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/QScintilla/Editor.py	Tue Nov 19 18:53:58 2019 +0100
@@ -1740,7 +1740,7 @@
                     filename, self.text(0), self)
                 language = "Python{0}".format(pyVer)
             if language in ['Python2', 'Python3', 'MicroPython', 'Ruby',
-                            'JavaScript']:
+                            'JavaScript', 'YAML', 'JSON']:
                 self.filetype = language
             else:
                 self.filetype = ""
--- a/eric6/Utilities/BackgroundService.py	Thu Nov 14 19:39:07 2019 +0100
+++ b/eric6/Utilities/BackgroundService.py	Tue Nov 19 18:53:58 2019 +0100
@@ -126,7 +126,7 @@
         connection = self.connections.get(lang)
         if connection is None:
             if fx != 'INIT':
-                # Avoid growing recursion deep which could itself result in an
+                # Avoid growing recursion depth which could itself result in an
                 # exception
                 QTimer.singleShot(
                     0,

eric ide

mercurial