浏览代码

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 年之前
父节点
当前提交
668dddc

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

@@ -110,6 +110,34 @@
       expect(result).toBeTruthy();
       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 () {
     describe('Hive specific', function () {
       it('should suggest expected words for "SLELECT "', function() {
       it('should suggest expected words for "SLELECT "', function() {
         var result = sqlSyntaxParser.parseSyntax('SLELECT ', '', 'hive');
         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,
                     text: lexer.match,
                     token: this.terminals_[symbol] || symbol,
                     token: this.terminals_[symbol] || symbol,
                     line: lexer.yylineno,
                     line: lexer.yylineno,
-                    loc: lexer.yylloc,
+                    loc: lexer.yylloc, ruleId: stack.slice(stack.length - 2, stack.length).join(),
                     expected: expected,
                     expected: expected,
                     recoverable: (error_rule_depth !== false)
                     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 = new Worker('/desktop/workers/aceSqlSyntaxWorker.js?bust=' + Math.random());
 
 
         self.aceSqlSyntaxWorker.onmessage = function(e) {
         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) {
             if (hueDebug.showSyntaxParseResult) {
               console.log(e.data.syntaxError);
               console.log(e.data.syntaxError);
             }
             }
@@ -4316,6 +4318,7 @@
                 });
                 });
               } else if (token.syntaxError) {
               } else if (token.syntaxError) {
                 huePubSub.publish('sql.syntax.dropdown.show', {
                 huePubSub.publish('sql.syntax.dropdown.show', {
+                  snippet: snippet,
                   data: token.syntaxError,
                   data: token.syntaxError,
                   editor: editor,
                   editor: editor,
                   range: range,
                   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 = {
     var SyntaxCheckerGlobals = {
       i18n: {
       i18n: {
         didYouMean: '${_('Did you mean')}',
         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;">
       <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 -->
         <!-- ko if: foreachVisible -->
         <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreachVisible: { data: filteredEntries, minHeight: 34, container: '.dropdown-menu' }">
         <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>
           <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>
         </ul>
         <!-- /ko -->
         <!-- /ko -->
         <!-- ko ifnot: foreachVisible -->
         <!-- ko ifnot: foreachVisible -->
         <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreach: filteredEntries">
         <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>
           <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>
         </ul>
         <!-- /ko -->
         <!-- /ko -->
       </div>
       </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;
         var self = this;
         self.disposalFunctions = [];
         self.disposalFunctions = [];
         self.selected = ko.observable();
         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;
           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) {
         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();
           hideSyntaxDropdown();
         });
         });
         self.disposalFunctions.push(function () {
         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..."
 echo "Creating SQL syntax parser..."
 jison sqlSyntaxParser.jison sql.jisonlex
 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)
 # 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
 cat license.txt sqlSyntaxParser.js > ../sqlSyntaxParser.js
 rm sqlSyntaxParser.jison
 rm sqlSyntaxParser.jison
 rm sqlSyntaxParser.js
 rm sqlSyntaxParser.js