Просмотр исходного кода

HUE-4031 [editor] Enable suppression of syntax errors based on rule

This enables the user to mark certain errors as ignored based on where in the statement the syntax error is found.
Johan Ahlen 8 лет назад
Родитель
Сommit
668dddc92a

+ 28 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/spec/sqlSyntaxParserSpec.js

@@ -110,6 +110,34 @@
       expect(result).toBeTruthy();
     });
 
+    var expectEqualIds = function (beforeA, afterA, beforeB, afterB) {
+      var resultA = sqlSyntaxParser.parseSyntax(beforeA, afterA);
+      var resultB = sqlSyntaxParser.parseSyntax(beforeB, afterB);
+      expect(resultA).toBeTruthy('"' + beforeA + '|' + afterA +'" was not reported as an error');
+      expect(resultB).toBeTruthy('"' + beforeB + '|' + afterB +'" was not reported as an error');
+      expect(resultA.ruleId).toEqual(resultB.ruleId);
+    };
+
+    var expectNonEqualIds = function (beforeA, afterA, beforeB, afterB) {
+      var resultA = sqlSyntaxParser.parseSyntax(beforeA, afterA);
+      var resultB = sqlSyntaxParser.parseSyntax(beforeB, afterB);
+      expect(resultA).toBeTruthy('"' + beforeA + '|' + afterA +'" was not reported as an error');
+      expect(resultB).toBeTruthy('"' + beforeB + '|' + afterB +'" was not reported as an error');
+      expect(resultA.ruleId).not.toEqual(resultB.ruleId);
+    };
+
+
+    it('should have unique rule IDs when the same rule is failing in different locations', function() {
+      expectEqualIds('SLELECT ', '', 'dlrop ', '');
+      expectEqualIds('SELECT * FORM ', '', 'SELECT * bla ', '');
+      expectEqualIds('DROP TABLE b.bla ERRROROR ', '', 'DROP TABLE c.cla OTHERERRRRORRR ', '');
+      expectEqualIds('SELECT * FROM a WHERE id = 1, a b SELECT ', '', 'SELECT id, foo FROM a WHERE a b SELECT', '');
+      expectEqualIds('SELECT * FROM a WHERE id = 1, a b SELECT ', '', 'SELECT id, foo FROM a WHERE a b SELECT', '');
+
+      expectNonEqualIds('slelect ', '', 'select * form ', '');
+    });
+
+
     describe('Hive specific', function () {
       it('should suggest expected words for "SLELECT "', function() {
         var result = sqlSyntaxParser.parseSyntax('SLELECT ', '', 'hive');

+ 1 - 1
desktop/core/src/desktop/static/desktop/js/autocomplete/sqlSyntaxParser.js

@@ -4615,7 +4615,7 @@ _handle_error:
                     text: lexer.match,
                     token: this.terminals_[symbol] || symbol,
                     line: lexer.yylineno,
-                    loc: lexer.yylloc,
+                    loc: lexer.yylloc, ruleId: stack.slice(stack.length - 2, stack.length).join(),
                     expected: expected,
                     recoverable: (error_rule_depth !== false)
                 });

+ 4 - 1
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -3606,7 +3606,9 @@
         self.aceSqlSyntaxWorker = new Worker('/desktop/workers/aceSqlSyntaxWorker.js?bust=' + Math.random());
 
         self.aceSqlSyntaxWorker.onmessage = function(e) {
-          if (e.data.syntaxError) {
+          var suppressedRules = ApiHelper.getInstance().getFromTotalStorage('hue.syntax.checker', 'suppressedRules', {});
+
+          if (e.data.syntaxError && !suppressedRules[self.snippet.id() + e.data.syntaxError.ruleId]) {
             if (hueDebug.showSyntaxParseResult) {
               console.log(e.data.syntaxError);
             }
@@ -4316,6 +4318,7 @@
                 });
               } else if (token.syntaxError) {
                 huePubSub.publish('sql.syntax.dropdown.show', {
+                  snippet: snippet,
                   data: token.syntaxError,
                   editor: editor,
                   range: range,

+ 2 - 1
desktop/core/src/desktop/templates/common_header_footer_components.mako

@@ -101,7 +101,8 @@ from metadata.conf import has_optimizer, OPTIMIZER
     var SyntaxCheckerGlobals = {
       i18n: {
         didYouMean: '${_('Did you mean')}',
-        expectedStatementEnd: '${_('Expected end of statement')}'
+        expectedStatementEnd: '${_('Expected end of statement')}',
+        suppressError: '${_('Ignore this type of error')}'
       }
     };
 

+ 10 - 0
desktop/core/src/desktop/templates/ko_components.mako

@@ -41,12 +41,22 @@ from desktop.views import _ko
       <div class="dropdown-menu" data-bind="visible: filteredEntries().length > 0" style="min-width: 190px; max-width: 250px; min-height: 34px; max-height: 200px;">
         <!-- ko if: foreachVisible -->
         <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreachVisible: { data: filteredEntries, minHeight: 34, container: '.dropdown-menu' }">
+          <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider -->
+          <li class="divider"></li>
+          <!-- /ko -->
+          <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
           <li><a href="javascript:void(0)" data-bind="text: typeof $data.label !== 'undefined' ? $data.label : $data, click: function () { $parent.value($data); }"></a></li>
+          <!-- /ko -->
         </ul>
         <!-- /ko -->
         <!-- ko ifnot: foreachVisible -->
         <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreach: filteredEntries">
+          <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider -->
+          <li class="divider"></li>
+          <!-- /ko -->
+          <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
           <li><a href="javascript:void(0)" data-bind="text: typeof $data.label !== 'undefined' ? $data.label : $data, click: function () { $parent.value($data); }"></a></li>
+          <!-- /ko -->
         </ul>
         <!-- /ko -->
       </div>

+ 21 - 3
desktop/core/src/desktop/templates/sql_syntax_dropdown.mako

@@ -46,12 +46,30 @@ from django.utils.translation import ugettext as _
         var self = this;
         self.disposalFunctions = [];
         self.selected = ko.observable();
-        self.expected = ko.observableArray($.map(params.data.expected, function (expected) {
+        var expected = $.map(params.data.expected, function (expected) {
           return expected.text;
-        }));
+        });
+        if (expected.length > 0) {
+          expected.push({
+            divider: true
+          });
+        }
+        expected.push({
+          label: SyntaxCheckerGlobals.i18n.suppressError,
+          suppressRule: params.data.ruleId
+        });
+        self.expected = ko.observableArray(expected);
 
         var selectedSub = self.selected.subscribe(function (newValue) {
-          params.editor.session.replace(params.range, newValue);
+          if (typeof newValue.suppressRule !== 'undefined') {
+            var suppressedRules = ApiHelper.getInstance().getFromTotalStorage('hue.syntax.checker', 'suppressedRules', {});
+            // TODO: Suppress on statement level instead of snippet once we have statement awareness in the snippet
+            suppressedRules[params.snippet.id() + newValue.suppressRule] = true;
+            ApiHelper.getInstance().setInTotalStorage('hue.syntax.checker', 'suppressedRules', suppressedRules);
+            huePubSub.publish('editor.refresh.statement.locations', params.snippet);
+          } else {
+            params.editor.session.replace(params.range, newValue);
+          }
           hideSyntaxDropdown();
         });
         self.disposalFunctions.push(function () {

+ 2 - 1
tools/jison/hue-jison.sh

@@ -45,7 +45,8 @@ cat syntax_header.jison sql_main.jison sql_valueExpression.jison  sql_alter.jiso
 echo "Creating SQL syntax parser..."
 jison sqlSyntaxParser.jison sql.jisonlex
 # Workaround for a parser bug where it reports the location of the previous token on error (pull-request submitted for jison)
-sed -i '' 's/loc: yyloc,/loc: lexer.yylloc,/' sqlSyntaxParser.js
+# We're also adding a ruleId to the parser error composed of the last two stack ID's and used for suppressing errors in the UI
+sed -i '' 's/loc: yyloc,/loc: lexer.yylloc, ruleId: stack.slice(stack.length - 2, stack.length).join(''),/' sqlSyntaxParser.js
 cat license.txt sqlSyntaxParser.js > ../sqlSyntaxParser.js
 rm sqlSyntaxParser.jison
 rm sqlSyntaxParser.js