diff --git a/validator/build.py b/validator/build.py index f863937d67e2..6328e41f7ac7 100755 --- a/validator/build.py +++ b/validator/build.py @@ -61,11 +61,12 @@ def CheckPrereqs(): 'Please feel free to edit the source and fix it to your needs.') # Ensure source files are available. - for f in ['validator-main.protoascii', 'validator.proto', - 'validator_gen_js.py', 'package.json', 'engine/validator.js', - 'engine/validator_test.js', 'engine/validator-in-browser.js', - 'engine/tokenize-css.js', 'engine/parse-css.js', - 'engine/parse-srcset.js', 'engine/parse-url.js']: + for f in [ + 'validator-main.protoascii', 'validator.proto', 'validator_gen_js.py', + 'package.json', 'engine/validator.js', 'engine/validator_test.js', + 'engine/validator-in-browser.js', 'engine/tokenize-css.js', + 'engine/parse-css.js', 'engine/parse-srcset.js', 'engine/parse-url.js' + ]: if not os.path.exists(f): Die('%s not found. Must run in amp_validator source directory.' % f) @@ -146,8 +147,8 @@ def GenValidatorPb2Py(out_dir): logging.info('entering ...') assert re.match(r'^[a-zA-Z_\-0-9]+$', out_dir), 'bad out_dir: %s' % out_dir - subprocess.check_call(['protoc', 'validator.proto', - '--python_out=%s' % out_dir]) + subprocess.check_call( + ['protoc', 'validator.proto', '--python_out=%s' % out_dir]) open('%s/__init__.py' % out_dir, 'w').close() logging.info('... done') @@ -199,6 +200,8 @@ def GenValidatorGeneratedJs(out_dir): specfile='%s/validator.protoascii' % out_dir, validator_pb2=validator_pb2, text_format=text_format, + html_format=None, + light=False, descriptor=descriptor, out=out) out.append('') @@ -208,6 +211,39 @@ def GenValidatorGeneratedJs(out_dir): logging.info('... done') +def GenValidatorGeneratedLightAmpJs(out_dir): + """Calls validator_gen_js to generate validator-generated-light-amp.js. + + Args: + out_dir: directory name of the output directory. Must not have slashes, + dots, etc. + """ + logging.info('entering ...') + assert re.match(r'^[a-zA-Z_\-0-9]+$', out_dir), 'bad out_dir: %s' % out_dir + + # These imports happen late, within this method because they don't necessarily + # exist when the module starts running, and the ones that probably do + # are checked by CheckPrereqs. + from google.protobuf import text_format + from google.protobuf import descriptor + from dist import validator_pb2 + import validator_gen_js + out = [] + validator_gen_js.GenerateValidatorGeneratedJs( + specfile='%s/validator.protoascii' % out_dir, + validator_pb2=validator_pb2, + text_format=text_format, + html_format=validator_pb2.TagSpec.AMP, + light=True, + descriptor=descriptor, + out=out) + out.append('') + f = open('%s/validator-generated-light-amp.js' % out_dir, 'w') + f.write('\n'.join(out)) + f.close() + logging.info('... done') + + def GenValidatorGeneratedMd(out_dir): """Calls validator_gen_md to generate validator-generated.md. @@ -246,14 +282,18 @@ def CompileWithClosure(js_files, closure_entry_points, output_file): output_file: name of the Javascript output file """ - cmd = ['java', '-jar', 'node_modules/google-closure-compiler/compiler.jar', - '--language_in=ECMASCRIPT6_STRICT', '--language_out=ES5_STRICT', - '--js_output_file=%s' % output_file, '--only_closure_dependencies'] + cmd = [ + 'java', '-jar', 'node_modules/google-closure-compiler/compiler.jar', + '--language_in=ECMASCRIPT6_STRICT', '--language_out=ES5_STRICT', + '--js_output_file=%s' % output_file, '--only_closure_dependencies' + ] cmd += ['--closure_entry_point=%s' % e for e in closure_entry_points] - cmd += ['node_modules/google-closure-library/closure/**.js', - '!node_modules/google-closure-library/closure/**_test.js', - 'node_modules/google-closure-library/third_party/closure/**.js', - '!node_modules/google-closure-library/third_party/closure/**_test.js'] + cmd += [ + 'node_modules/google-closure-library/closure/**.js', + '!node_modules/google-closure-library/closure/**_test.js', + 'node_modules/google-closure-library/third_party/closure/**.js', + '!node_modules/google-closure-library/third_party/closure/**_test.js' + ] cmd += js_files subprocess.check_call(cmd) @@ -266,16 +306,19 @@ def CompileValidatorMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/htmlparser.js', 'engine/parse-css.js', - 'engine/parse-srcset.js', 'engine/parse-url.js', - 'engine/tokenize-css.js', - '%s/validator-generated.js' % out_dir, - 'engine/validator-in-browser.js', 'engine/validator.js', - 'engine/amp4ads-parse-css.js', 'engine/dom-walker.js', - 'engine/htmlparser-interface.js'], - closure_entry_points=['amp.validator.validateString', - 'amp.validator.renderValidationResult', - 'amp.validator.renderErrorMessage'], + js_files=[ + 'engine/htmlparser.js', 'engine/parse-css.js', + 'engine/parse-srcset.js', 'engine/parse-url.js', + 'engine/tokenize-css.js', '%s/validator-generated.js' % out_dir, + 'engine/validator-in-browser.js', 'engine/validator.js', + 'engine/amp4ads-parse-css.js', 'engine/dom-walker.js', + 'engine/htmlparser-interface.js' + ], + closure_entry_points=[ + 'amp.validator.validateString', + 'amp.validator.renderValidationResult', + 'amp.validator.renderErrorMessage' + ], output_file='%s/validator_minified.js' % out_dir) logging.info('... done') @@ -290,9 +333,11 @@ def RunSmokeTest(out_dir, nodejs_cmd): logging.info('entering ...') # Run index.js on the minimum valid amp and observe that it passes. p = subprocess.Popen( - [nodejs_cmd, 'nodejs/index.js', '--validator_js', - '%s/validator_minified.js' % out_dir, - 'testdata/feature_tests/minimum_valid_amp.html'], + [ + nodejs_cmd, 'nodejs/index.js', '--validator_js', + '%s/validator_minified.js' % out_dir, + 'testdata/feature_tests/minimum_valid_amp.html' + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() @@ -303,9 +348,11 @@ def RunSmokeTest(out_dir, nodejs_cmd): # Run index.js on an empty file and observe that it fails. p = subprocess.Popen( - [nodejs_cmd, 'nodejs/index.js', '--validator_js', - '%s/validator_minified.js' % out_dir, - 'testdata/feature_tests/empty.html'], + [ + nodejs_cmd, 'nodejs/index.js', '--validator_js', + '%s/validator_minified.js' % out_dir, + 'testdata/feature_tests/empty.html' + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() @@ -345,13 +392,14 @@ def CompileValidatorTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/htmlparser.js', 'engine/parse-css.js', - 'engine/parse-srcset.js', 'engine/parse-url.js', - 'engine/tokenize-css.js', - '%s/validator-generated.js' % out_dir, - 'engine/validator-in-browser.js', 'engine/validator.js', - 'engine/amp4ads-parse-css.js', 'engine/htmlparser-interface.js', - 'engine/dom-walker.js', 'engine/validator_test.js'], + js_files=[ + 'engine/htmlparser.js', 'engine/parse-css.js', + 'engine/parse-srcset.js', 'engine/parse-url.js', + 'engine/tokenize-css.js', '%s/validator-generated.js' % out_dir, + 'engine/validator-in-browser.js', 'engine/validator.js', + 'engine/amp4ads-parse-css.js', 'engine/htmlparser-interface.js', + 'engine/dom-walker.js', 'engine/validator_test.js' + ], closure_entry_points=['amp.validator.ValidatorTest'], output_file='%s/validator_test_minified.js' % out_dir) logging.info('... success') @@ -366,12 +414,14 @@ def CompileValidatorLightTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/htmlparser.js', 'engine/parse-css.js', - 'engine/parse-srcset.js', 'engine/parse-url.js', - 'engine/tokenize-css.js', '%s/validator-generated.js' % out_dir, - 'engine/validator-in-browser.js', 'engine/validator.js', - 'engine/amp4ads-parse-css.js', 'engine/htmlparser-interface.js', - 'engine/dom-walker.js', 'engine/validator-light_test.js'], + js_files=[ + 'engine/htmlparser.js', 'engine/parse-css.js', + 'engine/parse-srcset.js', 'engine/parse-url.js', + 'engine/tokenize-css.js', '%s/validator-generated-light-amp.js' % + out_dir, 'engine/validator-in-browser.js', 'engine/validator.js', + 'engine/amp4ads-parse-css.js', 'engine/htmlparser-interface.js', + 'engine/dom-walker.js', 'engine/validator-light_test.js' + ], closure_entry_points=['amp.validator.ValidatorTest'], output_file='%s/validator-light_test_minified.js' % out_dir) logging.info('... success') @@ -386,8 +436,10 @@ def CompileHtmlparserTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/htmlparser.js', 'engine/htmlparser-interface.js', - 'engine/htmlparser_test.js'], + js_files=[ + 'engine/htmlparser.js', 'engine/htmlparser-interface.js', + 'engine/htmlparser_test.js' + ], closure_entry_points=['amp.htmlparser.HtmlParserTest'], output_file='%s/htmlparser_test_minified.js' % out_dir) logging.info('... success') @@ -402,10 +454,12 @@ def CompileParseCssTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/parse-css.js', 'engine/parse-url.js', - 'engine/tokenize-css.js', 'engine/css-selectors.js', - 'engine/json-testutil.js', 'engine/parse-css_test.js', - '%s/validator-generated.js' % out_dir], + js_files=[ + 'engine/parse-css.js', 'engine/parse-url.js', + 'engine/tokenize-css.js', 'engine/css-selectors.js', + 'engine/json-testutil.js', 'engine/parse-css_test.js', + '%s/validator-generated.js' % out_dir + ], closure_entry_points=['parse_css.ParseCssTest'], output_file='%s/parse-css_test_minified.js' % out_dir) logging.info('... success') @@ -420,10 +474,12 @@ def CompileParseUrlTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/parse-url.js', 'engine/parse-css.js', - 'engine/tokenize-css.js', 'engine/css-selectors.js', - 'engine/json-testutil.js', 'engine/parse-url_test.js', - '%s/validator-generated.js' % out_dir], + js_files=[ + 'engine/parse-url.js', 'engine/parse-css.js', + 'engine/tokenize-css.js', 'engine/css-selectors.js', + 'engine/json-testutil.js', 'engine/parse-url_test.js', + '%s/validator-generated.js' % out_dir + ], closure_entry_points=['parse_url.ParseURLTest'], output_file='%s/parse-url_test_minified.js' % out_dir) logging.info('... success') @@ -438,11 +494,12 @@ def CompileAmp4AdsParseCssTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/amp4ads-parse-css_test.js', 'engine/parse-css.js', - 'engine/parse-url.js', 'engine/amp4ads-parse-css.js', - 'engine/tokenize-css.js', 'engine/css-selectors.js', - 'engine/json-testutil.js', - '%s/validator-generated.js' % out_dir], + js_files=[ + 'engine/amp4ads-parse-css_test.js', 'engine/parse-css.js', + 'engine/parse-url.js', 'engine/amp4ads-parse-css.js', + 'engine/tokenize-css.js', 'engine/css-selectors.js', + 'engine/json-testutil.js', '%s/validator-generated.js' % out_dir + ], closure_entry_points=['parse_css.Amp4AdsParseCssTest'], output_file='%s/amp4ads-parse-css_test_minified.js' % out_dir) logging.info('... success') @@ -457,9 +514,10 @@ def CompileParseSrcsetTestMinified(out_dir): """ logging.info('entering ...') CompileWithClosure( - js_files=['engine/parse-srcset.js', 'engine/json-testutil.js', - 'engine/parse-srcset_test.js', - '%s/validator-generated.js' % out_dir], + js_files=[ + 'engine/parse-srcset.js', 'engine/json-testutil.js', + 'engine/parse-srcset_test.js', '%s/validator-generated.js' % out_dir + ], closure_entry_points=['parse_srcset.ParseSrcsetTest'], output_file='%s/parse-srcset_test_minified.js' % out_dir) logging.info('... success') @@ -527,6 +585,7 @@ def Main(): GenValidatorPb2Py(out_dir='dist') GenValidatorProtoascii(out_dir='dist') GenValidatorGeneratedJs(out_dir='dist') + GenValidatorGeneratedLightAmpJs(out_dir='dist') GenValidatorGeneratedMd(out_dir='dist') CompileValidatorMinified(out_dir='dist') RunSmokeTest(out_dir='dist', nodejs_cmd=nodejs_cmd) diff --git a/validator/engine/amp4ads-parse-css.js b/validator/engine/amp4ads-parse-css.js index d8ba22e3a2b9..88925d243e4f 100644 --- a/validator/engine/amp4ads-parse-css.js +++ b/validator/engine/amp4ads-parse-css.js @@ -18,11 +18,14 @@ goog.provide('parse_css.stripVendorPrefix'); goog.provide('parse_css.validateAmp4AdsCss'); +goog.require('amp.validator.LIGHT'); +goog.require('amp.validator.ValidationError'); goog.require('parse_css.DelimToken'); goog.require('parse_css.ErrorToken'); goog.require('parse_css.IdentToken'); goog.require('parse_css.RuleVisitor'); goog.require('parse_css.Stylesheet'); +goog.require('parse_css.TRIVIAL_ERROR_TOKEN'); /** * Strips vendor prefixes from identifiers, e.g. property names or names @@ -115,6 +118,10 @@ class Amp4AdsVisitor extends parse_css.RuleVisitor { } const ident = firstIdent(declaration.value); if (ident === 'fixed' || ident === 'sticky') { + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + return; + } this.errors.push(createParseErrorTokenAt( declaration, amp.validator.ValidationError.Code .CSS_SYNTAX_DISALLOWED_PROPERTY_VALUE, @@ -142,6 +149,10 @@ class Amp4AdsVisitor extends parse_css.RuleVisitor { parse_css.stripVendorPrefix(transitionedProperty); if (transitionedPropertyStripped !== 'opacity' && transitionedPropertyStripped !== 'transform') { + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + return; + } this.errors.push(createParseErrorTokenAt( decl, amp.validator.ValidationError.Code .CSS_SYNTAX_DISALLOWED_PROPERTY_VALUE_WITH_HINT, @@ -155,6 +166,10 @@ class Amp4AdsVisitor extends parse_css.RuleVisitor { // the only properties that may be transitioned are opacity and transform. if (this.inKeyframes !== null && name !== 'transform' && name !== 'opacity') { + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + return; + } this.errors.push(createParseErrorTokenAt( decl, amp.validator.ValidationError.Code .CSS_SYNTAX_PROPERTY_DISALLOWED_WITHIN_AT_RULE, @@ -178,6 +193,10 @@ class Amp4AdsVisitor extends parse_css.RuleVisitor { if (allowed.indexOf(parse_css.stripVendorPrefix(decl.name)) !== -1) { continue; } + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + return; + } this.errors.push(createParseErrorTokenAt( decl, amp.validator.ValidationError.Code .CSS_SYNTAX_PROPERTY_DISALLOWED_TOGETHER_WITH, @@ -188,6 +207,10 @@ class Amp4AdsVisitor extends parse_css.RuleVisitor { } // (2) Check that the rule is qualified with .amp-animate. if (!hasAmpAnimate(qualifiedRule)) { + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + return; + } this.errors.push(createParseErrorTokenAt( qualifiedRule, amp.validator.ValidationError.Code .CSS_SYNTAX_PROPERTY_REQUIRES_QUALIFICATION, diff --git a/validator/engine/css-selectors.js b/validator/engine/css-selectors.js index 5665fbc2f776..5bf1e6cfe951 100644 --- a/validator/engine/css-selectors.js +++ b/validator/engine/css-selectors.js @@ -33,7 +33,8 @@ goog.provide('parse_css.parseAnAttrSelector'); goog.provide('parse_css.parseAnIdSelector'); goog.provide('parse_css.parseSelectors'); goog.provide('parse_css.traverseSelectors'); -goog.require('amp.validator.GENERATE_DETAILED_ERRORS'); +goog.require('amp.validator.LIGHT'); +goog.require('amp.validator.ValidationError'); goog.require('goog.asserts'); goog.require('parse_css.ErrorToken'); goog.require('parse_css.TRIVIAL_ERROR_TOKEN'); @@ -99,7 +100,9 @@ parse_css.traverseSelectors = function(selectorNode, visitor) { /** @type {!parse_css.Selector} */ const node = toVisit.shift(); node.accept(visitor); - node.forEachChild(child => { toVisit.push(child); }); + node.forEachChild(child => { + toVisit.push(child); + }); } }; @@ -144,9 +147,11 @@ parse_css.TypeSelector = class extends parse_css.Selector { } /** @inheritDoc */ - accept(visitor) { visitor.visitTypeSelector(this); } + accept(visitor) { + visitor.visitTypeSelector(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.TypeSelector.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -226,12 +231,16 @@ parse_css.IdSelector = class extends parse_css.Selector { } /** @return {string} */ - toString() { return '#' + this.value; } + toString() { + return '#' + this.value; + } /** @inheritDoc */ - accept(visitor) { visitor.visitIdSelector(this); } + accept(visitor) { + visitor.visitIdSelector(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.IdSelector.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -287,9 +296,11 @@ parse_css.AttrSelector = class extends parse_css.Selector { } /** @inheritDoc */ - accept(visitor) { visitor.visitAttrSelector(this); } + accept(visitor) { + visitor.visitAttrSelector(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.AttrSelector.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -348,11 +359,10 @@ parse_css.parseAnAttrSelector = function(tokenStream) { } // Now parse the attribute name. This part is mandatory. if (!(tokenStream.current().tokenType === parse_css.TokenType.IDENT)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return newInvalidAttrSelectorError(start); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return newInvalidAttrSelectorError(start); } const ident = /** @type {!parse_css.IdentToken} */ (tokenStream.current()); const attrName = ident.value; @@ -407,9 +417,7 @@ parse_css.parseAnAttrSelector = function(tokenStream) { value = str.value; tokenStream.consume(); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return newInvalidAttrSelectorError(start); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } } @@ -420,11 +428,10 @@ parse_css.parseAnAttrSelector = function(tokenStream) { // The attribute selector must in any case terminate with a close square // token. if (tokenStream.current().tokenType !== parse_css.TokenType.CLOSE_SQUARE) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return newInvalidAttrSelectorError(start); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return newInvalidAttrSelectorError(start); } tokenStream.consume(); const selector = new parse_css.AttrSelector( @@ -465,9 +472,11 @@ parse_css.PseudoSelector = class extends parse_css.Selector { } /** @inheritDoc */ - accept(visitor) { visitor.visitPseudoSelector(this); } + accept(visitor) { + visitor.visitPseudoSelector(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.PseudoSelector.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -498,29 +507,26 @@ parse_css.parseAPseudoSelector = function(tokenStream) { isClass = false; tokenStream.consume(); } - let name = ''; - /** @type {!Array} */ - let func = []; if (tokenStream.current().tokenType === parse_css.TokenType.IDENT) { const ident = /** @type {!parse_css.IdentToken} */ (tokenStream.current()); - name = ident.value; + const name = ident.value; tokenStream.consume(); + return firstColon.copyPosTo( + new parse_css.PseudoSelector(isClass, name, [])); } else if ( tokenStream.current().tokenType === parse_css.TokenType.FUNCTION_TOKEN) { const funcToken = /** @type {!parse_css.FunctionToken} */ (tokenStream.current()); - name = funcToken.value; - func = parse_css.extractAFunction(tokenStream); + const func = parse_css.extractAFunction(tokenStream); tokenStream.consume(); - } else if (amp.validator.GENERATE_DETAILED_ERRORS) { + return firstColon.copyPosTo( + new parse_css.PseudoSelector(isClass, funcToken.value, func)); + } else if (!amp.validator.LIGHT) { return firstColon.copyPosTo(new parse_css.ErrorToken( amp.validator.ValidationError.Code.CSS_SYNTAX_ERROR_IN_PSEUDO_SELECTOR, ['style'])); - } else { - return parse_css.TRIVIAL_ERROR_TOKEN; } - return firstColon.copyPosTo( - new parse_css.PseudoSelector(isClass, name, func)); + return parse_css.TRIVIAL_ERROR_TOKEN; }; /** @@ -540,12 +546,16 @@ parse_css.ClassSelector = class extends parse_css.Selector { this.tokenType = parse_css.TokenType.CLASS_SELECTOR; } /** @return {string} */ - toString() { return '.' + this.value; } + toString() { + return '.' + this.value; + } /** @inheritDoc */ - accept(visitor) { visitor.visitClassSelector(this); } + accept(visitor) { + visitor.visitClassSelector(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.ClassSelector.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -611,9 +621,11 @@ parse_css.SimpleSelectorSequence = class extends parse_css.Selector { } /** @inheritDoc */ - accept(visitor) { visitor.visitSimpleSelectorSequence(this); } + accept(visitor) { + visitor.visitSimpleSelectorSequence(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.SimpleSelectorSequence.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -665,13 +677,12 @@ parse_css.parseASimpleSelectorSequence = function(tokenStream) { } else { if (typeSelector === null) { if (otherSelectors.length == 0) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return tokenStream.current().copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_MISSING_SELECTOR, - ['style'])); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return tokenStream.current().copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_MISSING_SELECTOR, + ['style'])); } // If no type selector is given then the universal selector is implied. typeSelector = start.copyPosTo(new parse_css.TypeSelector( @@ -722,9 +733,11 @@ parse_css.Combinator = class extends parse_css.Selector { } /** @inheritDoc */ - accept(visitor) { visitor.visitCombinator(this); } + accept(visitor) { + visitor.visitCombinator(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.Combinator.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -797,13 +810,12 @@ function isSimpleSelectorSequenceStart(token) { */ parse_css.parseASelector = function(tokenStream) { if (!isSimpleSelectorSequenceStart(tokenStream.current())) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return tokenStream.current().copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START, - ['style'])); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return tokenStream.current().copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START, + ['style'])); } const parsed = parse_css.parseASimpleSelectorSequence(tokenStream); if (parsed.tokenType === parse_css.TokenType.ERROR) { @@ -868,9 +880,11 @@ parse_css.SelectorsGroup = class extends parse_css.Selector { } /** @param {!parse_css.SelectorVisitor} visitor */ - accept(visitor) { visitor.visitSelectorsGroup(this); } + accept(visitor) { + visitor.visitSelectorsGroup(this); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.SelectorsGroup.prototype.toJSON = function() { const json = parse_css.Selector.prototype.toJSON.call(this); @@ -891,13 +905,12 @@ if (amp.validator.GENERATE_DETAILED_ERRORS) { */ parse_css.parseASelectorsGroup = function(tokenStream) { if (!isSimpleSelectorSequenceStart(tokenStream.current())) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return tokenStream.current().copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START, - ['style'])); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return tokenStream.current().copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_NOT_A_SELECTOR_START, + ['style'])); } const start = tokenStream.current(); const elements = [parse_css.parseASelector(tokenStream)]; @@ -923,14 +936,13 @@ parse_css.parseASelectorsGroup = function(tokenStream) { // We're about to claim success and return a selector, // but before we do, we check that no unparsed input remains. if (!(tokenStream.current().tokenType === parse_css.TokenType.EOF_TOKEN)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return tokenStream.current().copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code - .CSS_SYNTAX_UNPARSED_INPUT_REMAINS_IN_SELECTOR, - ['style'])); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return tokenStream.current().copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code + .CSS_SYNTAX_UNPARSED_INPUT_REMAINS_IN_SELECTOR, + ['style'])); } if (elements.length == 1) { return elements[0]; diff --git a/validator/engine/parse-css.js b/validator/engine/parse-css.js index 6260885db076..0afb02f28474 100644 --- a/validator/engine/parse-css.js +++ b/validator/engine/parse-css.js @@ -33,7 +33,7 @@ goog.provide('parse_css.extractAFunction'); goog.provide('parse_css.extractASimpleBlock'); goog.provide('parse_css.extractUrls'); goog.provide('parse_css.parseAStylesheet'); -goog.require('amp.validator.GENERATE_DETAILED_ERRORS'); +goog.require('amp.validator.LIGHT'); goog.require('amp.validator.ValidationError.Code'); goog.require('goog.asserts'); goog.require('parse_css.EOFToken'); @@ -97,21 +97,29 @@ parse_css.TokenStream = class { * Returns the token at the current position in the token stream. * @return {!parse_css.Token} */ - current() { return this.tokenAt(this.pos); } + current() { + return this.tokenAt(this.pos); + } /** * Returns the token at the next position in the token stream. * @return {!parse_css.Token} */ - next() { return this.tokenAt(this.pos + 1); } + next() { + return this.tokenAt(this.pos + 1); + } /** * Advances the stream by one. */ - consume() { this.pos++; } + consume() { + this.pos++; + } /** Rewinds to the previous position in the input. */ - reconsume() { this.pos--; } + reconsume() { + this.pos--; + } }; /** @@ -185,7 +193,7 @@ parse_css.Stylesheet = class extends parse_css.Rule { visitor.leaveStylesheet(this); } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.Stylesheet.prototype.toJSON = function() { const json = parse_css.Rule.prototype.toJSON.call(this); @@ -225,7 +233,7 @@ parse_css.AtRule = class extends parse_css.Rule { visitor.leaveAtRule(this); } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.AtRule.prototype.toJSON = function() { const json = parse_css.Rule.prototype.toJSON.call(this); @@ -257,7 +265,7 @@ parse_css.QualifiedRule = class extends parse_css.Rule { visitor.leaveQualifiedRule(this); } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.QualifiedRule.prototype.toJSON = function() { const json = parse_css.Rule.prototype.toJSON.call(this); @@ -289,7 +297,7 @@ parse_css.Declaration = class extends parse_css.Rule { visitor.leaveDeclaration(this); } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.Declaration.prototype.toJSON = function() { const json = parse_css.Rule.prototype.toJSON.call(this); @@ -436,7 +444,7 @@ class Canonicalizer { const startToken = /** @type {!parse_css.AtKeywordToken} */ (tokenStream.current()); const rule = new parse_css.AtRule(startToken.value); - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { startToken.copyPosTo(rule); } @@ -495,7 +503,7 @@ class Canonicalizer { tokenStream.current().tokenType !== parse_css.TokenType.AT_KEYWORD, 'Internal Error: parseAQualifiedRule precondition not met'); - if (!amp.validator.GENERATE_DETAILED_ERRORS && errors.length > 0) { + if (amp.validator.LIGHT && errors.length > 0) { return; } @@ -505,13 +513,14 @@ class Canonicalizer { tokenStream.consume(); const current = tokenStream.current().tokenType; if (current === parse_css.TokenType.EOF_TOKEN) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + } else { errors.push(rule.copyPosTo(new parse_css.ErrorToken( amp.validator.ValidationError.Code .CSS_SYNTAX_EOF_IN_PRELUDE_OF_QUALIFIED_RULE, ['style']))); - } else { - errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + } return; } @@ -538,7 +547,7 @@ class Canonicalizer { * @return {!Array} */ parseAListOfDeclarations(tokenList, errors) { - if (!amp.validator.GENERATE_DETAILED_ERRORS && errors.length > 0) { + if (amp.validator.LIGHT && errors.length > 0) { return []; } @@ -557,19 +566,18 @@ class Canonicalizer { // The CSS3 Parsing spec allows for AT rules inside lists of // declarations, but our grammar does not so we deviate a tiny bit here. // We consume an AT rule, but drop it and instead push an error token. - if (amp.validator.GENERATE_DETAILED_ERRORS) { - const atRule = this.parseAnAtRule(tokenStream, errors); - errors.push(atRule.copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_AT_RULE, - ['style', atRule.name]))); - } else { + if (amp.validator.LIGHT) { errors.push(parse_css.TRIVIAL_ERROR_TOKEN); return []; } + const atRule = this.parseAnAtRule(tokenStream, errors); + errors.push(atRule.copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_AT_RULE, + ['style', atRule.name]))); } else if (current === parse_css.TokenType.IDENT) { this.parseADeclaration(tokenStream, decls, errors); } else { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { errors.push(parse_css.TRIVIAL_ERROR_TOKEN); return []; } @@ -600,7 +608,7 @@ class Canonicalizer { tokenStream.current().tokenType === parse_css.TokenType.IDENT, 'Internal Error: parseADeclaration precondition not met'); - if (!amp.validator.GENERATE_DETAILED_ERRORS && errors.length > 0) { + if (amp.validator.LIGHT && errors.length > 0) { return; } @@ -615,7 +623,7 @@ class Canonicalizer { tokenStream.consume(); if (!(tokenStream.current().tokenType === parse_css.TokenType.COLON)) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { errors.push(parse_css.TRIVIAL_ERROR_TOKEN); return; } @@ -645,8 +653,8 @@ class Canonicalizer { continue; } else if ( decl.value[i].tokenType === parse_css.TokenType.IDENT && - /** @type {parse_css.IdentToken} */ (decl.value[i]) - .ASCIIMatch('important')) { + /** @type {parse_css.IdentToken} */ + (decl.value[i]).ASCIIMatch('important')) { foundImportant = true; } else if ( foundImportant && @@ -739,9 +747,9 @@ parse_css.extractASimpleBlock = function(tokenStream) { // Exclude the start token. Convert end token to EOF. const end = consumedTokens.length - 1; - consumedTokens[end] = amp.validator.GENERATE_DETAILED_ERRORS ? - consumedTokens[end].copyPosTo(new parse_css.EOFToken()) : - parse_css.TRIVIAL_EOF_TOKEN; + consumedTokens[end] = amp.validator.LIGHT ? + parse_css.TRIVIAL_EOF_TOKEN : + consumedTokens[end].copyPosTo(new parse_css.EOFToken()); return consumedTokens.slice(1); }; @@ -787,9 +795,9 @@ parse_css.extractAFunction = function(tokenStream) { // Convert end token to EOF. const end = consumedTokens.length - 1; - consumedTokens[end] = amp.validator.GENERATE_DETAILED_ERRORS ? - consumedTokens[end].copyPosTo(new parse_css.EOFToken()) : - parse_css.TRIVIAL_EOF_TOKEN; + consumedTokens[end] = amp.validator.LIGHT ? + parse_css.TRIVIAL_EOF_TOKEN : + consumedTokens[end].copyPosTo(new parse_css.EOFToken()); return consumedTokens; }; @@ -819,7 +827,7 @@ parse_css.ParsedCssUrl = class extends parse_css.Token { this.atRuleScope = ''; } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.ParsedCssUrl.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -921,13 +929,19 @@ class UrlFunctionVisitor extends parse_css.RuleVisitor { } /** @inheritDoc */ - visitAtRule(atRule) { this.atRuleScope = atRule.name; } + visitAtRule(atRule) { + this.atRuleScope = atRule.name; + } /** @inheritDoc */ - leaveAtRule(atRule) { this.atRuleScope = ''; } + leaveAtRule(atRule) { + this.atRuleScope = ''; + } /** @inheritDoc */ - visitQualifiedRule(qualifiedRule) { this.atRuleScope = ''; } + visitQualifiedRule(qualifiedRule) { + this.atRuleScope = ''; + } /** @inheritDoc */ visitDeclaration(declaration) { @@ -935,7 +949,7 @@ class UrlFunctionVisitor extends parse_css.RuleVisitor { goog.asserts.assert( declaration.value[declaration.value.length - 1].tokenType === parse_css.TokenType.EOF_TOKEN); - if (!amp.validator.GENERATE_DETAILED_ERRORS && this.errors.length > 0) { + if (amp.validator.LIGHT && this.errors.length > 0) { return; } for (let ii = 0; ii < declaration.value.length - 1;) { @@ -953,12 +967,12 @@ class UrlFunctionVisitor extends parse_css.RuleVisitor { const parsedUrl = new parse_css.ParsedCssUrl(); ii = parseUrlFunction(declaration.value, ii, parsedUrl); if (ii === -1) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); + } else { this.errors.push(token.copyPosTo(new parse_css.ErrorToken( amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, ['style']))); - } else { - this.errors.push(parse_css.TRIVIAL_ERROR_TOKEN); } return; } diff --git a/validator/engine/parse-srcset.js b/validator/engine/parse-srcset.js index 5da9150973bc..5743b3e583f2 100644 --- a/validator/engine/parse-srcset.js +++ b/validator/engine/parse-srcset.js @@ -16,6 +16,7 @@ */ goog.provide('parse_srcset.SrcsetParsingResult'); goog.provide('parse_srcset.parseSrcset'); +goog.require('amp.validator.LIGHT'); goog.require('amp.validator.ValidationError'); goog.require('goog.structs.Set'); @@ -30,13 +31,18 @@ parse_srcset.SrcsetSourceDef; /** * Return value for parseSrcset. - * @typedef {{ - * success: boolean, - * errorCode: !amp.validator.ValidationError.Code, - * srcsetImages: !Array - * }} + * @constructor @struct */ -parse_srcset.SrcsetParsingResult; +parse_srcset.SrcsetParsingResult = function() { + /** @type {boolean} */ + this.success = false; + if (!amp.validator.LIGHT) { + /** @type {!amp.validator.ValidationError.Code} */ + this.errorCode = amp.validator.ValidationError.Code.UNKNOWN_CODE; + } + /** @type {!Array} */ + this.srcsetImages = []; +}; /** * Parses the text representation of srcset into array of SrcsetSourceDef. @@ -87,8 +93,10 @@ parse_srcset.parseSrcset = function(srcset) { let remainingSrcset = srcset; /** @type {!goog.structs.Set} */ let seenWidthOrPixelDensity = new goog.structs.Set(); + /** @type {!parse_srcset.SrcsetParsingResult} */ + const result = new parse_srcset.SrcsetParsingResult(); /** @type {!Array} */ - let srcsetImages = []; + const srcsetImages = result.srcsetImages; let source; while (source = imageCandidateRegex.exec(srcset)) { let url = source[1]; @@ -99,11 +107,10 @@ parse_srcset.parseSrcset = function(srcset) { } // Duplicate width or pixel density in srcset. if (seenWidthOrPixelDensity.contains(widthOrPixelDensity)) { - return { - success: false, - errorCode: amp.validator.ValidationError.Code.DUPLICATE_DIMENSION, - srcsetImages: srcsetImages - }; + if (!amp.validator.LIGHT) { + result.errorCode = amp.validator.ValidationError.Code.DUPLICATE_DIMENSION; + } + return result; } seenWidthOrPixelDensity.add(widthOrPixelDensity); srcsetImages.push({url: url, widthOrPixelDensity: widthOrPixelDensity}); @@ -114,25 +121,26 @@ parse_srcset.parseSrcset = function(srcset) { } // More srcset, comma expected as separator for image candidates. if (comma === undefined) { - return { - success: false, - errorCode: amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - srcsetImages: srcsetImages - }; + if (!amp.validator.LIGHT) { + result.errorCode = amp.validator.ValidationError.Code.INVALID_ATTR_VALUE; + } + return result; } } // Regex didn't consume all of the srcset string if (remainingSrcset !== '') { - return { - success: false, - errorCode: amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - srcsetImages: srcsetImages - }; + if (!amp.validator.LIGHT) { + result.errorCode = amp.validator.ValidationError.Code.INVALID_ATTR_VALUE; + } + return result; } // Must have at least one image candidate. - return { - success: srcsetImages.length > 0, - errorCode: amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - srcsetImages: srcsetImages - }; + if (srcsetImages.length === 0) { + if (!amp.validator.LIGHT) { + result.errorCode = amp.validator.ValidationError.Code.INVALID_ATTR_VALUE; + } + return result; + } + result.success = true; + return result; }; diff --git a/validator/engine/tokenize-css.js b/validator/engine/tokenize-css.js index 50a44625bbb5..f2c07c6731b3 100644 --- a/validator/engine/tokenize-css.js +++ b/validator/engine/tokenize-css.js @@ -58,7 +58,7 @@ goog.provide('parse_css.TokenType'); goog.provide('parse_css.URLToken'); goog.provide('parse_css.WhitespaceToken'); goog.provide('parse_css.tokenize'); -goog.require('amp.validator.GENERATE_DETAILED_ERRORS'); +goog.require('amp.validator.LIGHT'); goog.require('amp.validator.ValidationError'); /** @@ -295,7 +295,9 @@ class Tokenizer { // Line number information. let eofToken; - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + eofToken = parse_css.TRIVIAL_EOF_TOKEN; + } else { /** @private @type {!Array} */ this.lineByPos_ = []; /** @private @type {!Array} */ @@ -315,21 +317,18 @@ class Tokenizer { eofToken = new parse_css.EOFToken(); eofToken.line = currentLine; eofToken.col = currentCol; - } else { - eofToken = parse_css.TRIVIAL_EOF_TOKEN; } let iterationCount = 0; while (!this.eof(this.next())) { const token = this.consumeAToken(); if (token.tokenType === parse_css.TokenType.ERROR) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - this.errors_.push(/** @type {!parse_css.ErrorToken} */ (token)); - } else { + if (amp.validator.LIGHT) { this.errors_.push(parse_css.TRIVIAL_ERROR_TOKEN); this.tokens_ = []; return; } + this.errors_.push(/** @type {!parse_css.ErrorToken} */ (token)); } else { this.tokens_.push(token); } @@ -360,7 +359,9 @@ class Tokenizer { /** * @return {!Array} */ - getTokens() { return this.tokens_; } + getTokens() { + return this.tokens_; + } /** * Returns the codepoint at the given position. @@ -421,7 +422,7 @@ class Tokenizer { this.consumeComments(); this.consume(); const mark = new parse_css.Token(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { mark.line = this.getLine(); mark.col = this.getCol(); } @@ -430,10 +431,10 @@ class Tokenizer { while (whitespace(this.next())) { this.consume(); } - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.WhitespaceToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_WHITESPACE_TOKEN; } - return TRIVIAL_WHITESPACE_TOKEN; + return mark.copyPosTo(new parse_css.WhitespaceToken()); } else if (this.code_ === /* '"' */ 0x22) { return mark.copyPosTo(this.consumeAStringToken()); } else if (this.code_ === /* '#' */ 0x23) { @@ -451,64 +452,64 @@ class Tokenizer { } return mark.copyPosTo(token); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_23; } - return TRIVIAL_DELIM_TOKEN_23; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '$' */ 0x24) { if (this.next() === /* '=' */ 0x3d) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - mark.copyPosTo(new parse_css.SuffixMatchToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_SUFFIX_MATCH_TOKEN; } - return TRIVIAL_SUFFIX_MATCH_TOKEN; + return mark.copyPosTo(new parse_css.SuffixMatchToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_24; } - return TRIVIAL_DELIM_TOKEN_24; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* ''' */ 0x27) { return mark.copyPosTo(this.consumeAStringToken()); } else if (this.code_ === /* '(' */ 0x28) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.OpenParenToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_OPEN_PAREN_TOKEN; } - return TRIVIAL_OPEN_PAREN_TOKEN; + return mark.copyPosTo(new parse_css.OpenParenToken()); } else if (this.code_ === /* ')' */ 0x29) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CloseParenToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CLOSE_PAREN_TOKEN; } - return TRIVIAL_CLOSE_PAREN_TOKEN; + return mark.copyPosTo(new parse_css.CloseParenToken()); } else if (this.code_ === /* '*' */ 0x2a) { if (this.next() === /* '=' */ 0x3d) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.SubstringMatchToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_SUBSTRING_MATCH_TOKEN; } - return TRIVIAL_SUBSTRING_MATCH_TOKEN; + return mark.copyPosTo(new parse_css.SubstringMatchToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_2A; } - return TRIVIAL_DELIM_TOKEN_2A; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '+' */ 0x2b) { if (this./*OK*/ startsWithANumber()) { this.reconsume(); return mark.copyPosTo(this.consumeANumericToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_2B; } - return TRIVIAL_DELIM_TOKEN_2B; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* ',' */ 0x2c) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CommaToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_COMMA_TOKEN; } - return TRIVIAL_COMMA_TOKEN; + return mark.copyPosTo(new parse_css.CommaToken()); } else if (this.code_ === /* '-' */ 0x2d) { if (this./*OK*/ startsWithANumber()) { this.reconsume(); @@ -516,52 +517,52 @@ class Tokenizer { } else if ( this.next(1) === /* '-' */ 0x2d && this.next(2) === /* '>' */ 0x3e) { this.consume(2); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CDCToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CDC_TOKEN; } - return TRIVIAL_CDC_TOKEN; + return mark.copyPosTo(new parse_css.CDCToken()); } else if (this./*OK*/ startsWithAnIdentifier()) { this.reconsume(); return mark.copyPosTo(this.consumeAnIdentlikeToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_2D; } - return TRIVIAL_DELIM_TOKEN_2D; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '.' */ 0x2e) { if (this./*OK*/ startsWithANumber()) { this.reconsume(); return mark.copyPosTo(this.consumeANumericToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_2E; } - return TRIVIAL_DELIM_TOKEN_2E; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* ':' */ 0x3a) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.ColonToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_COLON_TOKEN; } - return TRIVIAL_COLON_TOKEN; + return mark.copyPosTo(new parse_css.ColonToken()); } else if (this.code_ === /* ';' */ 0x3b) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.SemicolonToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_SEMICOLON_TOKEN; } - return TRIVIAL_SEMICOLON_TOKEN; + return mark.copyPosTo(new parse_css.SemicolonToken()); } else if (this.code_ === /* '<' */ 0x3c) { if (this.next(1) === /* '!' */ 0x21 && this.next(2) === /* '-' */ 0x2d && this.next(3) === /* '-' */ 0x2d) { this.consume(3); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CDOToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CDO_TOKEN; } - return TRIVIAL_CDO_TOKEN; + return mark.copyPosTo(new parse_css.CDOToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_3C; } - return TRIVIAL_DELIM_TOKEN_3C; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '@' */ 0x40) { if (this.wouldStartAnIdentifier( @@ -570,91 +571,91 @@ class Tokenizer { token.value = this.consumeAName(); return mark.copyPosTo(token); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_40; } - return TRIVIAL_DELIM_TOKEN_40; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '[' */ 0x5b) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.OpenSquareToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_OPEN_SQUARE_TOKEN; } - return TRIVIAL_OPEN_SQUARE_TOKEN; + return mark.copyPosTo(new parse_css.OpenSquareToken()); } else if (this.code_ === /* '\' */ 0x5c) { if (this./*OK*/ startsWithAValidEscape()) { this.reconsume(); return mark.copyPosTo(this.consumeAnIdentlikeToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - // This condition happens if we are in consumeAToken (this method), - // the current codepoint is 0x5c (\) and the next codepoint is a - // newline (\n). - return mark.copyPosTo(new parse_css.ErrorToken( - amp.validator.ValidationError.Code - .CSS_SYNTAX_STRAY_TRAILING_BACKSLASH, - ['style'])); + if (amp.validator.LIGHT) { + return parse_css.TRIVIAL_ERROR_TOKEN; } - return parse_css.TRIVIAL_ERROR_TOKEN; + // This condition happens if we are in consumeAToken (this method), + // the current codepoint is 0x5c (\) and the next codepoint is a + // newline (\n). + return mark.copyPosTo(new parse_css.ErrorToken( + amp.validator.ValidationError.Code + .CSS_SYNTAX_STRAY_TRAILING_BACKSLASH, + ['style'])); } } else if (this.code_ === /* ']' */ 0x5d) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CloseSquareToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CLOSE_SQUARE_TOKEN; } - return TRIVIAL_CLOSE_SQUARE_TOKEN; + return mark.copyPosTo(new parse_css.CloseSquareToken()); } else if (this.code_ === /* '^' */ 0x5e) { if (this.next() === /* '=' */ 0x3d) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.PrefixMatchToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_PREFIX_MATCH_TOKEN; } - return TRIVIAL_PREFIX_MATCH_TOKEN; + return mark.copyPosTo(new parse_css.PrefixMatchToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_5E; } - return TRIVIAL_DELIM_TOKEN_5E; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '{' */ 0x7b) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.OpenCurlyToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_OPEN_CURLY_TOKEN; } - return TRIVIAL_OPEN_CURLY_TOKEN; + return mark.copyPosTo(new parse_css.OpenCurlyToken()); } else if (this.code_ === /* '|' */ 0x7c) { if (this.next() === /* '=' */ 0x3d) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DashMatchToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_DASH_MATCH_TOKEN; } - return TRIVIAL_DASH_MATCH_TOKEN; + return mark.copyPosTo(new parse_css.DashMatchToken()); } else if (this.next() === /* '|' */ 0x7c) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.ColumnToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_COLUMN_TOKEN; } - return TRIVIAL_COLUMN_TOKEN; + return mark.copyPosTo(new parse_css.ColumnToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_7C; } - return TRIVIAL_DELIM_TOKEN_7C; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (this.code_ === /* '}' */ 0x7d) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.CloseCurlyToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CLOSE_CURLY_TOKEN; } - return TRIVIAL_CLOSE_CURLY_TOKEN; + return mark.copyPosTo(new parse_css.CloseCurlyToken()); } else if (this.code_ === /* '~' */ 0x7e) { if (this.next() === /* '=' */ 0x3d) { this.consume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.IncludeMatchToken()); + if (amp.validator.LIGHT) { + return TRIVIAL_CLOSE_CURLY_TOKEN; } - return TRIVIAL_CLOSE_CURLY_TOKEN; + return mark.copyPosTo(new parse_css.IncludeMatchToken()); } else { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.DelimToken(this.code_)); + if (amp.validator.LIGHT) { + return TRIVIAL_DELIM_TOKEN_7E; } - return TRIVIAL_DELIM_TOKEN_7E; + return mark.copyPosTo(new parse_css.DelimToken(this.code_)); } } else if (digit(this.code_)) { this.reconsume(); @@ -663,10 +664,10 @@ class Tokenizer { this.reconsume(); return mark.copyPosTo(this.consumeAnIdentlikeToken()); } else if (this.eof()) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return mark.copyPosTo(new parse_css.EOFToken()); + if (amp.validator.LIGHT) { + return parse_css.TRIVIAL_EOF_TOKEN; } - return parse_css.TRIVIAL_EOF_TOKEN; + return mark.copyPosTo(new parse_css.EOFToken()); } else { const token = new parse_css.DelimToken(this.code_); return mark.copyPosTo(token); @@ -679,7 +680,7 @@ class Tokenizer { */ consumeComments() { const mark = new parse_css.Token(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { mark.line = this.getLine(); mark.col = this.getCol(); } @@ -691,15 +692,15 @@ class Tokenizer { this.consume(); break; } else if (this.eof()) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + this.errors_.push(parse_css.TRIVIAL_ERROR_TOKEN); + } else { // For example "h1 { color: red; } \* " would emit this parse error // at the end of the string. this.errors_.push(mark.copyPosTo(new parse_css.ErrorToken( amp.validator.ValidationError.Code .CSS_SYNTAX_UNTERMINATED_COMMENT, ['style']))); - } else { - this.errors_.push(parse_css.TRIVIAL_ERROR_TOKEN); } return; } @@ -753,8 +754,9 @@ class Tokenizer { token.value = name; return token; } else if ( - whitespace(this.next()) && (this.next(2) === /* '"' */ 0x22 || - this.next(2) === /* ''' */ 0x27)) { + whitespace(this.next()) && + (this.next(2) === /* '"' */ 0x22 || + this.next(2) === /* ''' */ 0x27)) { const token = new parse_css.FunctionToken(); token.value = name; return token; @@ -793,13 +795,12 @@ class Tokenizer { return token; } else if (newline(this.code_)) { this.reconsume(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_UNTERMINATED_STRING, - ['style']); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_UNTERMINATED_STRING, + ['style']); } else if (this.code_ === /* '\' */ 0x5c) { if (this.eof(this.next())) { continue; @@ -841,36 +842,31 @@ class Tokenizer { return token; } else { this.consumeTheRemnantsOfABadURL(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, - ['style']); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, ['style']); } } else if ( this.code_ === /* '"' */ 0x22 || this.code_ === /* ''' */ 0x27 || this.code_ === /* '(' */ 0x28 || nonPrintable(this.code_)) { this.consumeTheRemnantsOfABadURL(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, ['style']); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, ['style']); } else if (this.code_ === /* '\' */ 0x5c) { if (this./*OK*/ startsWithAValidEscape()) { token.value += stringFromCode(this.consumeEscape()); } else { this.consumeTheRemnantsOfABadURL(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return new parse_css.ErrorToken( - amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, - ['style']); - } else { + if (amp.validator.LIGHT) { return parse_css.TRIVIAL_ERROR_TOKEN; } + return new parse_css.ErrorToken( + amp.validator.ValidationError.Code.CSS_SYNTAX_BAD_URL, ['style']); } } else { token.value += stringFromCode(this.code_); @@ -903,7 +899,11 @@ class Tokenizer { this.consume(); } let value = parseInt( - digits.map(function(x) { return String.fromCharCode(x); }).join(''), + digits + .map(function(x) { + return String.fromCharCode(x); + }) + .join(''), 16); if (value > maxAllowedCodepoint) { value = 0xfffd; @@ -1224,7 +1224,7 @@ const TokenType_NamesById = [ */ parse_css.Token = class { constructor() { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { /** @type {number} */ this.line = 1; /** @type {number} */ @@ -1239,7 +1239,7 @@ parse_css.Token = class { * @template T */ copyPosTo(other) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { other.line = this.line; other.col = this.col; } @@ -1249,7 +1249,7 @@ parse_css.Token = class { /** @type {!parse_css.TokenType} */ parse_css.Token.prototype.tokenType = parse_css.TokenType.UNKNOWN; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @return {!Object} */ parse_css.Token.prototype.toJSON = function() { return { @@ -1267,23 +1267,25 @@ if (amp.validator.GENERATE_DETAILED_ERRORS) { */ parse_css.ErrorToken = class extends parse_css.Token { /** - * @param {!amp.validator.ValidationError.Code} code - * @param {!Array} params + * @param {amp.validator.ValidationError.Code=} opt_code + * @param {!Array=} opt_params */ - constructor(code, params) { + constructor(opt_code, opt_params) { super(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { + goog.asserts.assert(opt_code !== undefined); + goog.asserts.assert(opt_params !== undefined); /** @type {!amp.validator.ValidationError.Code} */ - this.code = code; + this.code = opt_code; /** @type {!Array} */ - this.params = params; + this.params = opt_params; } } }; /** @type {!parse_css.TokenType} */ parse_css.ErrorToken.prototype.tokenType = parse_css.TokenType.ERROR; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.ErrorToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -1293,11 +1295,12 @@ if (amp.validator.GENERATE_DETAILED_ERRORS) { }; } -/** - * @type {!parse_css.ErrorToken} - */ -parse_css.TRIVIAL_ERROR_TOKEN = new parse_css.ErrorToken( - amp.validator.ValidationError.Code.UNKNOWN_CODE, []); +if (amp.validator.LIGHT) { + /** + * @type {!parse_css.ErrorToken} + */ + parse_css.TRIVIAL_ERROR_TOKEN = new parse_css.ErrorToken(); +} parse_css.WhitespaceToken = class extends parse_css.Token {}; /** @type {!parse_css.TokenType} */ @@ -1456,7 +1459,7 @@ const TRIVIAL_DELIM_TOKEN_5E = new parse_css.DelimToken(0x5E); const TRIVIAL_DELIM_TOKEN_7C = new parse_css.DelimToken(0x7C); const TRIVIAL_DELIM_TOKEN_7E = new parse_css.DelimToken(0x7E); -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.DelimToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -1476,9 +1479,11 @@ parse_css.StringValuedToken = class extends parse_css.Token { * @param {string} str * @return {boolean} */ - ASCIIMatch(str) { return this.value.toLowerCase() === str.toLowerCase(); } + ASCIIMatch(str) { + return this.value.toLowerCase() === str.toLowerCase(); + } }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.StringValuedToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -1512,7 +1517,7 @@ parse_css.HashToken = class extends parse_css.StringValuedToken { /** @type {!parse_css.TokenType} */ parse_css.HashToken.prototype.tokenType = parse_css.TokenType.HASH; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.HashToken.prototype.toJSON = function() { const json = parse_css.StringValuedToken.prototype.toJSON.call(this); @@ -1543,7 +1548,7 @@ parse_css.NumberToken = class extends parse_css.Token { /** @type {!parse_css.TokenType} */ parse_css.NumberToken.prototype.tokenType = parse_css.TokenType.NUMBER; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.NumberToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -1566,7 +1571,7 @@ parse_css.PercentageToken = class extends parse_css.Token { /** @type {!parse_css.TokenType} */ parse_css.PercentageToken.prototype.tokenType = parse_css.TokenType.PERCENTAGE; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.PercentageToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); @@ -1592,7 +1597,7 @@ parse_css.DimensionToken = class extends parse_css.Token { /** @type {!parse_css.TokenType} */ parse_css.DimensionToken.prototype.tokenType = parse_css.TokenType.DIMENSION; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** @inheritDoc */ parse_css.DimensionToken.prototype.toJSON = function() { const json = parse_css.Token.prototype.toJSON.call(this); diff --git a/validator/engine/validator-light_test.js b/validator/engine/validator-light_test.js index 332b325df1ff..8869cc5bd357 100644 --- a/validator/engine/validator-light_test.js +++ b/validator/engine/validator-light_test.js @@ -145,15 +145,17 @@ function validateString(inputDocContents) { * against the golden file content. */ ValidatorTestCase.prototype.run = function() { - const observed = validateString(this.ampHtmlFileContents).status; - if (observed === this.expectedOutput) { + const code = validateString(this.ampHtmlFileContents).status; + const observedStatus = + ['UNKNOWN', 'PASS', 'FAIL'][/** @type {number} */ (code)]; + if (observedStatus === this.expectedOutput) { return; } let message = ''; if (this.expectedOutputFile != null) { message = '\n' + this.expectedOutputFile + ':1:0\n'; } - message += 'expected:\n' + this.expectedOutput + '\nsaw:\n' + observed; + message += 'expected:\n' + this.expectedOutput + '\nsaw:\n' + observedStatus; assert.fail('', '', message, ''); }; diff --git a/validator/engine/validator.js b/validator/engine/validator.js index 53927e92e788..b675e0249dd8 100644 --- a/validator/engine/validator.js +++ b/validator/engine/validator.js @@ -30,7 +30,7 @@ goog.require('amp.validator.AtRuleSpec'); goog.require('amp.validator.AtRuleSpec.BlockType'); goog.require('amp.validator.CssSpec'); goog.require('amp.validator.ErrorCategory'); -goog.require('amp.validator.GENERATE_DETAILED_ERRORS'); +goog.require('amp.validator.LIGHT'); goog.require('amp.validator.ReferencePoint'); goog.require('amp.validator.TagSpec'); goog.require('amp.validator.VALIDATE_CSS'); @@ -476,24 +476,13 @@ class ParsedAttrSpecs { */ constructor(rules) { /** @type {!Array>} */ - this.attrLists = []; + this.attrLists = rules.directAttrLists; /** @type {!Array} */ - this.globalAttrs = []; + this.globalAttrs = rules.globalAttrs; /** @type {!Array} */ - this.layoutAttrs = []; - - for (const attrList of rules.attrLists) { - if (attrList.name === '$AMP_LAYOUT_ATTRS') { - this.layoutAttrs = attrList.attrs; - } else if (attrList.name === '$GLOBAL_ATTRS') { - this.globalAttrs = attrList.attrs; - } - this.attrLists.push(attrList.attrs); - } - goog.asserts.assert(this.layoutAttrs.length > 0, 'layout attrs not found'); - goog.asserts.assert(this.globalAttrs.length > 0, 'global attrs not found'); + this.ampLayoutAttrs = rules.ampLayoutAttrs; /** * The AttrSpec instances, indexed by attr spec ids. @@ -612,7 +601,7 @@ class ParsedTagSpec { // (1) layout attrs. if (tagSpec.ampLayout !== null && !this.isReferencePoint_) { - this.mergeAttrs(parsedAttrSpecs.layoutAttrs, parsedAttrSpecs); + this.mergeAttrs(parsedAttrSpecs.ampLayoutAttrs, parsedAttrSpecs); } // (2) attributes specified within |tagSpec|. this.mergeAttrs(tagSpec.attrs, parsedAttrSpecs); @@ -689,11 +678,10 @@ class ParsedTagSpec { * @return {!Array} */ getAlsoRequiresTagWarning() { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return this.spec_.alsoRequiresTagWarning; - } else { + if (amp.validator.LIGHT) { return []; } + return this.spec_.alsoRequiresTagWarning; } /** @@ -703,11 +691,10 @@ class ParsedTagSpec { * @return {!Array} */ getExtensionUnusedUnlessTagPresent() { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return this.spec_.extensionUnusedUnlessTagPresent; - } else { + if (amp.validator.LIGHT) { return []; } + return this.spec_.extensionUnusedUnlessTagPresent; } /** @@ -827,7 +814,7 @@ amp.validator.ValidationResult.prototype.mergeFrom = function(other) { if (other.status !== amp.validator.ValidationResult.Status.UNKNOWN) { this.status = other.status; } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { Array.prototype.push.apply(this.errors, other.errors); } }; @@ -840,7 +827,7 @@ amp.validator.ValidationResult.prototype.copyFrom = function(other) { goog.asserts.assert(this.status !== null); goog.asserts.assert(other.status !== null); this.status = other.status; - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { this.errors = []; Array.prototype.push.apply(this.errors, other.errors); } @@ -929,7 +916,7 @@ class ChildTagMatcher { if (childTags.childTagNameOneof.length > 0) { const names = childTags.childTagNameOneof; if (names.indexOf(tagName) === -1) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { const allowedNames = '[\'' + names.join('\', \'') + '\']'; context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -951,7 +938,7 @@ class ChildTagMatcher { (this.numChildTagsSeen_ - 1) === 0) { const names = childTags.firstChildTagNameOneof; if (names.indexOf(tagName) == -1) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { const allowedNames = '[\'' + names.join('\', \'') + '\']'; context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -984,21 +971,20 @@ class ChildTagMatcher { if (expected === -1 || expected === this.numChildTagsSeen_) { return; } - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.INCORRECT_NUM_CHILD_TAGS, - this.lineCol_, - /* params */ - [ - getTagSpecName(this.parentSpec_), expected.toString(), - this.numChildTagsSeen_.toString() - ], - this.parentSpec_.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.INCORRECT_NUM_CHILD_TAGS, + this.lineCol_, + /* params */ + [ + getTagSpecName(this.parentSpec_), expected.toString(), + this.numChildTagsSeen_.toString() + ], + this.parentSpec_.specUrl, result); } } @@ -1078,7 +1064,7 @@ class ReferencePointMatcher { goog.asserts.assert( resultForBestAttempt.status === amp.validator.ValidationResult.Status.FAIL); - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } @@ -1159,40 +1145,38 @@ class ReferencePointMatcher { const RefPointTagSpecId = /** @type {number} */ (p.tagSpecName); if (p.mandatory && !referencePointByCount.hasOwnProperty(RefPointTagSpecId)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code - .MANDATORY_REFERENCE_POINT_MISSING, - this.lineCol_, - /*params*/ - [ - this.parsedValidatorRules_.getReferencePointName(p), - this.parsedReferencePoints_.parentTagSpecName() - ], - this.parsedReferencePoints_.parentSpecUrl(), result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code + .MANDATORY_REFERENCE_POINT_MISSING, + this.lineCol_, + /*params*/ + [ + this.parsedValidatorRules_.getReferencePointName(p), + this.parsedReferencePoints_.parentTagSpecName() + ], + this.parsedReferencePoints_.parentSpecUrl(), result); } if (p.unique && referencePointByCount.hasOwnProperty(RefPointTagSpecId) && referencePointByCount[RefPointTagSpecId] !== 1) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.DUPLICATE_REFERENCE_POINT, - this.lineCol_, - /*params*/ - [ - this.parsedValidatorRules_.getReferencePointName(p), - this.parsedReferencePoints_.parentTagSpecName() - ], - this.parsedReferencePoints_.parentSpecUrl(), result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.DUPLICATE_REFERENCE_POINT, + this.lineCol_, + /*params*/ + [ + this.parsedValidatorRules_.getReferencePointName(p), + this.parsedReferencePoints_.parentTagSpecName() + ], + this.parsedReferencePoints_.parentSpecUrl(), result); } } } @@ -1264,7 +1248,7 @@ class TagStack { */ enterTag(tagName, context, result, encounteredAttrs) { let maybeDataAmpReportTestValue = null; - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { for (let i = 0; i < encounteredAttrs.length; i += 2) { const attrName = encounteredAttrs[i]; const attrValue = encounteredAttrs[i + 1]; @@ -1459,15 +1443,15 @@ class InvalidAtRuleVisitor extends parse_css.RuleVisitor { /** @inheritDoc */ visitAtRule(atRule) { if (!isAtRuleValid(this.cssSpec, atRule.name)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + this.result.status = amp.validator.ValidationResult.Status.FAIL; + } else { this.context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.CSS_SYNTAX_INVALID_AT_RULE, new LineCol(atRule.line, atRule.col), /* params */[getTagSpecName(this.tagSpec), atRule.name], /* url */ '', this.result); - } else { - this.result.status = amp.validator.ValidationResult.Status.FAIL; } } } @@ -1540,7 +1524,7 @@ class CdataMatcher { // we've advanced past the tag. This information gets filled in // by Context.setCdataMatcher. - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { // FIXME /** @private @type {!LineCol} */ this.lineCol_ = new LineCol(1, 0); } @@ -1617,19 +1601,19 @@ class CdataMatcher { if (cdataSpec.maxBytes !== -1) { const bytes = byteLength(cdata); if (bytes > cdataSpec.maxBytes) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.STYLESHEET_TOO_LONG, - context.getDocLocator(), - /* params */ - [ - getTagSpecName(this.tagSpec_), bytes.toString(), - cdataSpec.maxBytes.toString() - ], - cdataSpec.maxBytesSpecUrl, validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; + } else { + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.STYLESHEET_TOO_LONG, + context.getDocLocator(), + /* params */ + [ + getTagSpecName(this.tagSpec_), bytes.toString(), + cdataSpec.maxBytes.toString() + ], + cdataSpec.maxBytesSpecUrl, validationResult); } // We return early if the byte length is violated as parsing // really long stylesheets is slow and not worth our time. @@ -1646,24 +1630,25 @@ class CdataMatcher { // Mandatory CDATA exact match if (cdataSpec.mandatoryCdata !== null) { if (cdataSpec.mandatoryCdata !== cdata) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code - .MANDATORY_CDATA_MISSING_OR_INCORRECT, - context.getDocLocator(), - /* params */[getTagSpecName(this.tagSpec_)], - this.tagSpec_.specUrl, validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code + .MANDATORY_CDATA_MISSING_OR_INCORRECT, + context.getDocLocator(), + /* params */[getTagSpecName(this.tagSpec_)], this.tagSpec_.specUrl, + validationResult); } // We return early if the cdata has an exact match rule. The // spec shouldn't have an exact match rule that doesn't validate. return; } else if (this.hasCdataRegex()) { if (!this.getCdataRegex().test(cdata)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + validationResult.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code @@ -1671,15 +1656,13 @@ class CdataMatcher { context.getDocLocator(), /* params */[getTagSpecName(this.tagSpec_)], this.tagSpec_.specUrl, validationResult); - } else { - validationResult.status = amp.validator.ValidationResult.Status.FAIL; } return; } } else if (cdataSpec.cssSpec !== null) { if (amp.validator.VALIDATE_CSS) { this.matchCss_(cdata, cdataSpec.cssSpec, context, validationResult); - if (!amp.validator.GENERATE_DETAILED_ERRORS && + if (amp.validator.LIGHT && validationResult.status == amp.validator.ValidationResult.Status.FAIL) { return; @@ -1697,19 +1680,18 @@ class CdataMatcher { for (const blacklist of cdataSpec.blacklistedCdataRegex) { const blacklistRegex = new RegExp(blacklist.regex, 'i'); if (blacklistRegex.test(cdata)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.CDATA_VIOLATES_BLACKLIST, - context.getDocLocator(), - /* params */ - [getTagSpecName(this.tagSpec_), blacklist.errorMessage], - this.tagSpec_.specUrl, validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.CDATA_VIOLATES_BLACKLIST, + context.getDocLocator(), + /* params */ + [getTagSpecName(this.tagSpec_), blacklist.errorMessage], + this.tagSpec_.specUrl, validationResult); } } } @@ -1729,13 +1711,10 @@ class CdataMatcher { const cssErrors = []; /** @type {!Array} */ const tokenList = parse_css.tokenize( - cdata, - amp.validator.GENERATE_DETAILED_ERRORS ? this.getLineCol().getLine() : - undefined, - amp.validator.GENERATE_DETAILED_ERRORS ? this.getLineCol().getCol() : - undefined, + cdata, amp.validator.LIGHT ? undefined : this.getLineCol().getLine(), + amp.validator.LIGHT ? undefined : this.getLineCol().getCol(), cssErrors); - if (!amp.validator.GENERATE_DETAILED_ERRORS && cssErrors.length > 0) { + if (amp.validator.LIGHT && cssErrors.length > 0) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } @@ -1745,7 +1724,7 @@ class CdataMatcher { const sheet = parse_css.parseAStylesheet( tokenList, cssParsingConfig.atRuleSpec, cssParsingConfig.defaultSpec, cssErrors); - if (!amp.validator.GENERATE_DETAILED_ERRORS && cssErrors.length > 0) { + if (amp.validator.LIGHT && cssErrors.length > 0) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } @@ -1759,7 +1738,12 @@ class CdataMatcher { parse_css.validateAmp4AdsCss(sheet, cssErrors); } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + if (cssErrors.length > 0) { + validationResult.status = amp.validator.ValidationResult.Status.FAIL; + return; + } + } else { for (const errorToken of cssErrors) { // Override the first parameter with the name of this style tag. let params = errorToken.params; @@ -1770,16 +1754,13 @@ class CdataMatcher { new LineCol(errorToken.line, errorToken.col), params, /* url */ '', validationResult); } - } else if (cssErrors.length > 0) { - validationResult.status = amp.validator.ValidationResult.Status.FAIL; - return; } const parsedFontUrlSpec = new ParsedUrlSpec(cssSpec.fontUrlSpec); const parsedImageUrlSpec = new ParsedUrlSpec(cssSpec.imageUrlSpec); for (const url of parsedUrls) { - const adapter = amp.validator.GENERATE_DETAILED_ERRORS ? - new UrlErrorInStylesheetAdapter(url.line, url.col) : - null; + const adapter = amp.validator.LIGHT ? + null : + new UrlErrorInStylesheetAdapter(url.line, url.col); validateUrlAndProtocol( ((url.atRuleScope === 'font-face') ? parsedFontUrlSpec : parsedImageUrlSpec), @@ -2002,7 +1983,7 @@ class Context { /** @param {?CdataMatcher} matcher */ setCdataMatcher(matcher) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { // We store away the position from when the matcher was created // so we can use it to generate error messages relating to the // opening tag. @@ -2019,7 +2000,7 @@ class Context { /** @param {?ChildTagMatcher} matcher */ setChildTagMatcher(matcher) { - if (amp.validator.GENERATE_DETAILED_ERRORS && matcher !== null) { + if (!amp.validator.LIGHT && matcher !== null) { matcher.setLineCol( new LineCol(this.docLocator_.getLine(), this.docLocator_.getCol())); } @@ -2028,7 +2009,7 @@ class Context { /** @param {?ReferencePointMatcher} matcher */ setReferencePointMatcher(matcher) { - if (amp.validator.GENERATE_DETAILED_ERRORS && matcher !== null) { + if (!amp.validator.LIGHT && matcher !== null) { matcher.setLineCol( new LineCol(this.docLocator_.getLine(), this.docLocator_.getCol())); } @@ -2222,29 +2203,29 @@ function validateAttrValueUrl( maybeUris.push(attrValue); } else { if (attrValue === '') { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MISSING_URL, context.getDocLocator(), /* params */[attrName, getTagSpecName(tagSpec)], tagSpec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } /** @type {!parse_srcset.SrcsetParsingResult} */ const parseResult = parse_srcset.parseSrcset(attrValue); if (!parseResult.success) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, parseResult.errorCode, context.getDocLocator(), /* params */[attrName, getTagSpecName(tagSpec), attrValue], tagSpec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -2255,22 +2236,21 @@ function validateAttrValueUrl( } } if (maybeUris.length === 0) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MISSING_URL, context.getDocLocator(), /* params */[attrName, getTagSpecName(tagSpec)], tagSpec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } sortAndUniquify(maybeUris); - const adapter = amp.validator.GENERATE_DETAILED_ERRORS ? - new UrlErrorInAttrAdapter(attrName) : - null; + const adapter = + amp.validator.LIGHT ? null : new UrlErrorInAttrAdapter(attrName); for (const maybeUri of maybeUris) { const unescapedMaybeUri = goog.string.unescapeEntities(maybeUri); validateUrlAndProtocol( @@ -2296,19 +2276,19 @@ function validateUrlAndProtocol( const onlyWhitespaceRe = /^[\s\xa0]*$/; // includes non-breaking space if (urlStr.match(onlyWhitespaceRe) !== null && (spec.allowEmpty === null || spec.allowEmpty === false)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - adapter.missingUrl(context, tagSpec, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; + } else { + adapter.missingUrl(context, tagSpec, result); } return; } const url = new parse_url.URL(urlStr); if (!url.isValid) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - adapter.invalidUrl(context, urlStr, tagSpec, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; + } else { + adapter.invalidUrl(context, urlStr, tagSpec, result); } return; } @@ -2329,27 +2309,27 @@ function validateUrlAndProtocol( protocol = url.protocol; } if (protocol.length > 0 && !parsedUrlSpec.isAllowedProtocol(protocol)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - adapter.invalidUrlProtocol(context, protocol, tagSpec, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; + } else { + adapter.invalidUrlProtocol(context, protocol, tagSpec, result); } return; } if (!spec.allowRelative && (!url.hasProtocol || url.protocol.length == 0)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - adapter.disallowedRelativeUrl(context, urlStr, tagSpec, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; + } else { + adapter.disallowedRelativeUrl(context, urlStr, tagSpec, result); } return; } const domain = url.host.toLowerCase(); if (domain.length > 0 && parsedUrlSpec.isDisallowedDomain(domain)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - adapter.disallowedDomain(context, domain, tagSpec, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; + } else { + adapter.disallowedDomain(context, domain, tagSpec, result); } return; } @@ -2382,56 +2362,57 @@ function validateAttrValueProperties( for (const name of names) { const value = properties[name]; if (!parsedAttrSpec.valuePropertyByName_.hasOwnProperty(name)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code - .DISALLOWED_PROPERTY_IN_ATTR_VALUE, - context.getDocLocator(), - /* params */[name, attrName, getTagSpecName(tagSpec)], - tagSpec.specUrl, result); - continue; - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.DISALLOWED_PROPERTY_IN_ATTR_VALUE, + context.getDocLocator(), + /* params */[name, attrName, getTagSpecName(tagSpec)], + tagSpec.specUrl, result); + continue; } const propertySpec = parsedAttrSpec.valuePropertyByName_[name]; if (propertySpec.value !== null) { if (propertySpec.value !== value.toLowerCase()) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code - .INVALID_PROPERTY_VALUE_IN_ATTR_VALUE, - context.getDocLocator(), - /* params */[name, attrName, getTagSpecName(tagSpec), value], - tagSpec.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code + .INVALID_PROPERTY_VALUE_IN_ATTR_VALUE, + context.getDocLocator(), + /* params */[name, attrName, getTagSpecName(tagSpec), value], + tagSpec.specUrl, result); } } else if (propertySpec.valueDouble !== null) { if (parseFloat(value) !== propertySpec.valueDouble) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code - .INVALID_PROPERTY_VALUE_IN_ATTR_VALUE, - context.getDocLocator(), - /* params */[name, attrName, getTagSpecName(tagSpec), value], - tagSpec.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code + .INVALID_PROPERTY_VALUE_IN_ATTR_VALUE, + context.getDocLocator(), + /* params */[name, attrName, getTagSpecName(tagSpec), value], + tagSpec.specUrl, result); } } } const notSeen = subtractDiff(parsedAttrSpec.mandatoryValuePropertyNames_, names); - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + if (notSeen.length > 0) { + result.status = amp.validator.ValidationResult.Status.FAIL; + return; + } + } else { for (const name of notSeen) { context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -2441,11 +2422,6 @@ function validateAttrValueProperties( /* params */[name, attrName, getTagSpecName(tagSpec)], tagSpec.specUrl, result); } - } else { - if (notSeen.length > 0) { - result.status = amp.validator.ValidationResult.Status.FAIL; - return; - } } } @@ -2476,32 +2452,30 @@ function validateNonTemplateAttrValueAgainstSpec( if ((spec.value == '') && (attrValue == attrName)) { return; } - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - context.getDocLocator(), - /* params */[attrName, getTagSpecName(tagSpec), attrValue], - tagSpec.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, + context.getDocLocator(), + /* params */[attrName, getTagSpecName(tagSpec), attrValue], + tagSpec.specUrl, result); } else if (spec.valueCasei !== null) { if (attrValue.toLowerCase() === spec.valueCasei) { return; } - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - context.getDocLocator(), - /* params */[attrName, getTagSpecName(tagSpec), attrValue], - tagSpec.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, + context.getDocLocator(), + /* params */[attrName, getTagSpecName(tagSpec), attrValue], + tagSpec.specUrl, result); } else if ( parsedAttrSpec.hasValueRegex() || parsedAttrSpec.hasValueRegexCasei()) { let valueRegex; @@ -2511,17 +2485,16 @@ function validateNonTemplateAttrValueAgainstSpec( valueRegex = parsedAttrSpec.getValueRegexCasei(); } if (!valueRegex.test(attrValue)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, - context.getDocLocator(), - /* params */[attrName, getTagSpecName(tagSpec), attrValue], - tagSpec.specUrl, result); - } else { + if (amp.validator.LIGHT) { result.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, + context.getDocLocator(), + /* params */[attrName, getTagSpecName(tagSpec), attrValue], + tagSpec.specUrl, result); } } else if (spec.valueUrl !== null) { validateAttrValueUrl( @@ -2542,12 +2515,11 @@ function parseLayout(layout) { return amp.validator.AmpLayout.Layout.UNKNOWN; } const normLayout = layout.toUpperCase().replace('-', '_'); - for (const k in amp.validator.AmpLayout.Layout) { - if (amp.validator.AmpLayout.Layout[k] == normLayout) { - return amp.validator.AmpLayout.Layout[k]; - } + const idx = amp.validator.AmpLayout.Layout_NamesByIndex.indexOf(normLayout); + if (idx === -1) { + return amp.validator.AmpLayout.Layout.UNKNOWN; } - return amp.validator.AmpLayout.Layout.UNKNOWN; + return amp.validator.AmpLayout.Layout_ValuesByIndex[idx]; } /** @@ -2686,14 +2658,9 @@ function CalculateLayout(inputLayout, width, height, sizesAttr, heightsAttr) { * @return {boolean} */ function shouldRecordTagspecValidated(tag, tagSpecId, tagSpecIdsToTrack) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - return tag.mandatory || tag.unique || tag.uniqueWarning || - tag.requires.length > 0 || - tagSpecIdsToTrack.hasOwnProperty(tagSpecId); - } else { - return tag.mandatory || tag.unique || tag.requires.length > 0 || - tagSpecIdsToTrack.hasOwnProperty(tagSpecId); - } + return tag.mandatory || tag.unique || tag.requires.length > 0 || + tagSpecIdsToTrack.hasOwnProperty(tagSpecId) || + (!amp.validator.LIGHT && tag.uniqueWarning); } /** @@ -2767,23 +2734,22 @@ function validateParentTag(parsedTagSpec, context, validationResult) { const spec = parsedTagSpec.getSpec(); if (spec.mandatoryParent !== null && spec.mandatoryParent !== context.getTagStack().getParent()) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - // Output a parent/child error using CSS Child Selector syntax which is - // both succinct and should be well understood by web developers. - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.WRONG_PARENT_TAG, - context.getDocLocator(), - /* params */ - [ - getTagSpecName(spec), - context.getTagStack().getParent().toLowerCase(), - spec.mandatoryParent.toLowerCase() - ], - spec.specUrl, validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; + return; } + // Output a parent/child error using CSS Child Selector syntax which is + // both succinct and should be well understood by web developers. + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.WRONG_PARENT_TAG, + context.getDocLocator(), + /* params */ + [ + getTagSpecName(spec), context.getTagStack().getParent().toLowerCase(), + spec.mandatoryParent.toLowerCase() + ], + spec.specUrl, validationResult); } } @@ -2798,7 +2764,9 @@ function validateAncestorTags(parsedTagSpec, context, validationResult) { if (spec.mandatoryAncestor !== null) { const mandatoryAncestor = spec.mandatoryAncestor; if (!context.getTagStack().hasAncestor(mandatoryAncestor)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + validationResult.status = amp.validator.ValidationResult.Status.FAIL; + } else { if (spec.mandatoryAncestorSuggestedAlternative !== null) { context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -2820,15 +2788,15 @@ function validateAncestorTags(parsedTagSpec, context, validationResult) { [spec.tagName.toLowerCase(), mandatoryAncestor.toLowerCase()], spec.specUrl, validationResult); } - } else { - validationResult.status = amp.validator.ValidationResult.Status.FAIL; } return; } } for (const disallowedAncestor of spec.disallowedAncestor) { if (context.getTagStack().hasAncestor(disallowedAncestor)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + validationResult.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.DISALLOWED_TAG_ANCESTOR, @@ -2836,8 +2804,6 @@ function validateAncestorTags(parsedTagSpec, context, validationResult) { /* params */ [spec.tagName.toLowerCase(), disallowedAncestor.toLowerCase()], spec.specUrl, validationResult); - } else { - validationResult.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -2877,45 +2843,45 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { const inputLayout = parseLayout(layoutAttr); if (layoutAttr !== undefined && inputLayout === amp.validator.AmpLayout.Layout.UNKNOWN) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */['layout', getTagSpecName(spec), layoutAttr], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } const inputWidth = new amp.validator.CssLength(widthAttr, /* allowAuto */ true); if (!inputWidth.isValid) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */['width', getTagSpecName(spec), widthAttr], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } const inputHeight = new amp.validator.CssLength(heightAttr, /* allowAuto */ true); if (!inputHeight.isValid) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */['height', getTagSpecName(spec), heightAttr], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -2928,22 +2894,24 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { // height="auto" is only allowed if the layout is FLEX_ITEM. if (height.isAuto && layout !== amp.validator.AmpLayout.Layout.FLEX_ITEM) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */['height', getTagSpecName(spec), heightAttr], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } // Does the tag support the computed layout? if (spec.ampLayout.supportedLayouts.indexOf(layout) === -1) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { const code = layoutAttr === undefined ? amp.validator.ValidationError.Code.IMPLIED_LAYOUT_INVALID : amp.validator.ValidationError.Code.SPECIFIED_LAYOUT_INVALID; @@ -2951,8 +2919,6 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { amp.validator.ValidationError.Severity.ERROR, code, context.getDocLocator(), /* params */[layout, getTagSpecName(spec)], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -2961,20 +2927,22 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { layout === amp.validator.AmpLayout.Layout.FIXED_HEIGHT || layout === amp.validator.AmpLayout.Layout.RESPONSIVE) && !height.isSet) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MANDATORY_ATTR_MISSING, context.getDocLocator(), /* params */['height', getTagSpecName(spec)], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } if (layout === amp.validator.AmpLayout.Layout.FIXED_HEIGHT && width.isSet && !width.isAuto) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.ATTR_VALUE_REQUIRED_BY_LAYOUT, @@ -2982,41 +2950,41 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { /* params */ [widthAttr, 'width', getTagSpecName(spec), 'FIXED_HEIGHT', 'auto'], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } if (layout === amp.validator.AmpLayout.Layout.FIXED || layout === amp.validator.AmpLayout.Layout.RESPONSIVE) { if (!width.isSet) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MANDATORY_ATTR_MISSING, context.getDocLocator(), /* params */['width', getTagSpecName(spec)], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } else if (width.isAuto) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */['width', getTagSpecName(spec), 'auto'], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } } if (layout === amp.validator.AmpLayout.Layout.RESPONSIVE && width.unit !== height.unit) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code @@ -3024,14 +2992,14 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { context.getDocLocator(), /* params */[getTagSpecName(spec), width.unit, height.unit], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } if (heightsAttr !== undefined && layout !== amp.validator.AmpLayout.Layout.RESPONSIVE) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { const code = layoutAttr === undefined ? amp.validator.ValidationError.Code.ATTR_DISALLOWED_BY_IMPLIED_LAYOUT : amp.validator.ValidationError.Code @@ -3041,8 +3009,6 @@ function validateLayout(parsedTagSpec, context, attrsByKey, result) { context.getDocLocator(), /* params */['heights', getTagSpecName(spec), layout], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -3079,14 +3045,17 @@ function validateAttrNotFoundInSpec(parsedTagSpec, context, attrName, result) { // At this point, it's an error either way, but we try to give a // more specific error in the case of Mustache template characters. - if (amp.validator.GENERATE_DETAILED_ERRORS) { - if (attrName.indexOf('{{') !== -1) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.TEMPLATE_IN_ATTR_NAME, - context.getDocLocator(), - /* params */[attrName, getTagSpecName(parsedTagSpec.getSpec())], - context.getRules().templateSpecUrl, result); + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + return; + } + if (attrName.indexOf('{{') !== -1) { + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.TEMPLATE_IN_ATTR_NAME, + context.getDocLocator(), + /* params */[attrName, getTagSpecName(parsedTagSpec.getSpec())], + context.getRules().templateSpecUrl, result); } else { context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -3094,9 +3063,6 @@ function validateAttrNotFoundInSpec(parsedTagSpec, context, attrName, result) { context.getDocLocator(), /* params */[attrName, getTagSpecName(parsedTagSpec.getSpec())], parsedTagSpec.getSpec().specUrl, result); - } - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } } @@ -3111,7 +3077,9 @@ function validateAttrNotFoundInSpec(parsedTagSpec, context, attrName, result) { function validateAttrValueBelowTemplateTag( parsedTagSpec, context, attrName, attrValue, result) { if (attrValueHasUnescapedTemplateSyntax(attrValue)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { const spec = parsedTagSpec.getSpec(); context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -3119,11 +3087,11 @@ function validateAttrValueBelowTemplateTag( context.getDocLocator(), /* params */[attrName, getTagSpecName(spec), attrValue], context.getRules().templateSpecUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } } else if (attrValueHasPartialsTemplateSyntax(attrValue)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { const spec = parsedTagSpec.getSpec(); context.addError( amp.validator.ValidationError.Severity.ERROR, @@ -3131,8 +3099,6 @@ function validateAttrValueBelowTemplateTag( context.getDocLocator(), /* params */[attrName, getTagSpecName(spec), attrValue], context.getRules().templateSpecUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } } } @@ -3229,20 +3195,17 @@ function validateAttributes( continue; } const parsedAttrSpec = parsedAttrSpecs.getByAttrSpecId(attrId); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - if (parsedAttrSpec.getSpec().deprecation !== null) { - context.addError( - amp.validator.ValidationError.Severity.WARNING, - amp.validator.ValidationError.Code.DEPRECATED_ATTR, - context.getDocLocator(), - /* params */ - [ - attrName, getTagSpecName(spec), - parsedAttrSpec.getSpec().deprecation - ], - parsedAttrSpec.getSpec().deprecationUrl, result); - // Deprecation is only a warning, so we don't return. - } + if (!amp.validator.LIGHT && parsedAttrSpec.getSpec().deprecation !== null) { + context.addError( + amp.validator.ValidationError.Severity.WARNING, + amp.validator.ValidationError.Code.DEPRECATED_ATTR, + context.getDocLocator(), + /* params */ + [ + attrName, getTagSpecName(spec), parsedAttrSpec.getSpec().deprecation + ], + parsedAttrSpec.getSpec().deprecationUrl, result); + // Deprecation is only a warning, so we don't return. } if (!hasTemplateAncestor || !attrValueHasTemplateSyntax(attrValue)) { validateNonTemplateAttrValueAgainstSpec( @@ -3255,15 +3218,15 @@ function validateAttributes( const decodedAttrValue = decodeAttrValue(attrValue); if (parsedAttrSpec.getBlacklistedValueRegex().test(attrValue) || parsedAttrSpec.getBlacklistedValueRegex().test(decodedAttrValue)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.INVALID_ATTR_VALUE, context.getDocLocator(), /* params */[attrName, getTagSpecName(spec), attrValue], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -3273,14 +3236,14 @@ function validateAttributes( } if (parsedSpec.getSpec().tagName === 'BASE' && attrName === 'href' && context.hasSeenUrl()) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.BASE_TAG_MUST_PRECEED_ALL_URLS, context.getDocLocator(), /* params */[context.firstSeenUrlTagName()], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -3290,7 +3253,9 @@ function validateAttributes( if (parsedAttrSpec.getSpec().mandatoryOneof && mandatoryOneofsSeen.hasOwnProperty( parsedAttrSpec.getSpec().mandatoryOneof)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MUTUALLY_EXCLUSIVE_ATTRS, @@ -3298,8 +3263,6 @@ function validateAttributes( /* params */ [getTagSpecName(spec), parsedAttrSpec.getSpec().mandatoryOneof], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } return; } @@ -3322,15 +3285,15 @@ function validateAttributes( // alternatives were present, we report that an attribute is missing. for (const mandatoryOneof of parsedTagSpec.getMandatoryOneofs()) { if (!mandatoryOneofsSeen.hasOwnProperty(mandatoryOneof)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MANDATORY_ONEOF_ATTR_MISSING, context.getDocLocator(), /* params */[getTagSpecName(spec), mandatoryOneof], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } } } @@ -3341,7 +3304,9 @@ function validateAttributes( } const attrId = attrsByName[alsoRequiresAttr]; if (!attrspecsValidated.hasOwnProperty(attrId)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.ATTR_REQUIRED_BUT_MISSING, @@ -3352,15 +3317,16 @@ function validateAttributes( getTagSpecName(spec), triggerSpec.getAttrName() ], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; } } } } for (const mandatory of parsedTagSpec.getMandatoryAttrIds()) { if (!mandatoryAttrsSeen.hasOwnProperty(mandatory)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + result.status = amp.validator.ValidationResult.Status.FAIL; + break; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.MANDATORY_ATTR_MISSING, @@ -3371,9 +3337,6 @@ function validateAttributes( getTagSpecName(spec) ], spec.specUrl, result); - } else { - result.status = amp.validator.ValidationResult.Status.FAIL; - break; } } } @@ -3506,7 +3469,7 @@ function validateTagAgainstSpec( validateAncestorTags(parsedSpec, context, resultForAttempt); if (resultForAttempt.status === amp.validator.ValidationResult.Status.FAIL) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { resultForBestAttempt.status = amp.validator.ValidationResult.Status.FAIL; return; } @@ -3537,16 +3500,14 @@ function validateTagAgainstSpec( resultForBestAttempt.copyFrom(resultForAttempt); const spec = parsedSpec.getSpec(); - if (amp.validator.GENERATE_DETAILED_ERRORS) { - if (spec.deprecation !== null) { - context.addError( - amp.validator.ValidationError.Severity.WARNING, - amp.validator.ValidationError.Code.DEPRECATED_TAG, - context.getDocLocator(), - /* params */[getTagSpecName(spec), spec.deprecation], - spec.deprecationUrl, resultForBestAttempt); - // Deprecation is only a warning, so we don't return. - } + if (!amp.validator.LIGHT && spec.deprecation !== null) { + context.addError( + amp.validator.ValidationError.Severity.WARNING, + amp.validator.ValidationError.Code.DEPRECATED_TAG, + context.getDocLocator(), + /* params */[getTagSpecName(spec), spec.deprecation], + spec.deprecationUrl, resultForBestAttempt); + // Deprecation is only a warning, so we don't return. } for (const condition of spec.satisfies) { @@ -3562,27 +3523,25 @@ function validateTagAgainstSpec( // to be unique, we've found an error that we must report. if (!isUnique) { if (spec.unique) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + resultForBestAttempt.status = + amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.DUPLICATE_UNIQUE_TAG, context.getDocLocator(), /* params */[getTagSpecName(spec)], spec.specUrl, resultForBestAttempt); - } else { - resultForBestAttempt.status = - amp.validator.ValidationResult.Status.FAIL; } return; - } else if (amp.validator.GENERATE_DETAILED_ERRORS) { - if (spec.uniqueWarning) { - context.addError( - amp.validator.ValidationError.Severity.WARNING, - amp.validator.ValidationError.Code.DUPLICATE_UNIQUE_TAG_WARNING, - context.getDocLocator(), - /* params */[getTagSpecName(spec)], spec.specUrl, - resultForBestAttempt); - } + } else if (!amp.validator.LIGHT && spec.uniqueWarning) { + context.addError( + amp.validator.ValidationError.Severity.WARNING, + amp.validator.ValidationError.Code.DUPLICATE_UNIQUE_TAG_WARNING, + context.getDocLocator(), + /* params */[getTagSpecName(spec)], spec.specUrl, + resultForBestAttempt); } } } @@ -3605,7 +3564,10 @@ function validateTagAgainstSpec( // conflicting matcher, there can be only one. const currentMatcher = context.getTagStack().currentReferencePointMatcher(); if (currentMatcher !== null) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + resultForBestAttempt.status = + amp.validator.ValidationResult.Status.FAIL; + } else { context.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.TAG_REFERENCE_POINT_CONFLICT, @@ -3617,9 +3579,6 @@ function validateTagAgainstSpec( ], currentMatcher.getParsedReferencePoints().parentSpecUrl(), resultForBestAttempt); - } else { - resultForBestAttempt.status = - amp.validator.ValidationResult.Status.FAIL; } } else { context.setReferencePointMatcher(new ReferencePointMatcher( @@ -3628,18 +3587,6 @@ function validateTagAgainstSpec( } } -/** - * True iff tagspec's html_format matches the AMP html_format. - * @param {amp.validator.TagSpec} tagSpec - * @param {amp.validator.TagSpec.HtmlFormat} htmlFormat - * @return {boolean} - */ -function isTagSpecCorrectHtmlFormat(tagSpec, htmlFormat) { - // Empty html_format field implies tagspec applies to all docs. - return tagSpec.htmlFormat.length === 0 || - tagSpec.htmlFormat.indexOf(htmlFormat) !== -1; -} - /** * This wrapper class provides access to the validation rules. * @private @@ -3672,10 +3619,19 @@ class ParsedValidatorRules { * @private */ this.mandatoryTagSpecs_ = []; - - /** @private @type {amp.validator.TagSpec.HtmlFormat} */ - this.htmlFormat_ = - /** @type {amp.validator.TagSpec.HtmlFormat} */ (htmlFormat); + if (!amp.validator.LIGHT) { + /** + * @type {!function(!amp.validator.TagSpec) : boolean} + * @private + */ + this.isTagSpecCorrectHtmlFormat_ = function(tagSpec) { + const castedHtmlFormat = + /** @type {amp.validator.TagSpec.HtmlFormat} */ ( + /** @type {*} */ (htmlFormat)); + return tagSpec.htmlFormat.length === 0 || + tagSpec.htmlFormat.indexOf(castedHtmlFormat) !== -1; + }; + } /** * @type {!ParsedAttrSpecs} @@ -3688,10 +3644,10 @@ class ParsedValidatorRules { var numTags = this.rules_.tags.length; for (var tagSpecId = 0; tagSpecId < numTags; ++tagSpecId) { const tag = this.rules_.tags[tagSpecId]; - if (!isTagSpecCorrectHtmlFormat(tag, this.htmlFormat_)) { - continue; - } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { + if (!this.isTagSpecCorrectHtmlFormat_(tag)) { + continue; + } if (tag.alsoRequiresTagWarning.length > 0) { this.tagSpecIdsToTrack_[tagSpecId] = true; } @@ -3721,7 +3677,7 @@ class ParsedValidatorRules { this.mandatoryTagSpecs_.push(tagSpecId); } } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { /** * @typedef {{ format: string, specificity: number }} */ @@ -3792,18 +3748,16 @@ class ParsedValidatorRules { maybeEmitMandatoryTagValidationErrors(context, validationResult) { for (const tagSpecId of this.mandatoryTagSpecs_) { if (!context.getTagspecsValidated().hasOwnProperty(tagSpecId)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - const spec = this.getByTagSpecId(tagSpecId).getSpec(); - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.MANDATORY_TAG_MISSING, - context.getDocLocator(), - /* params */[getTagSpecName(spec)], spec.specUrl, - validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } + const spec = this.getByTagSpecId(tagSpecId).getSpec(); + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.MANDATORY_TAG_MISSING, + context.getDocLocator(), + /* params */[getTagSpecName(spec)], spec.specUrl, validationResult); } } } @@ -3824,23 +3778,24 @@ class ParsedValidatorRules { const spec = this.getByTagSpecId(tagSpecId); for (const condition of spec.requires()) { if (!context.satisfiesCondition(condition)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - context.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.TAG_REQUIRED_BY_MISSING, - context.getDocLocator(), - /* params */ - [context.getInternedString(condition), - getTagSpecName(spec.getSpec())], - spec.getSpec().specUrl, validationResult); - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } + context.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.TAG_REQUIRED_BY_MISSING, + context.getDocLocator(), + /* params */ + [ + context.getInternedString(condition), + getTagSpecName(spec.getSpec()) + ], + spec.getSpec().specUrl, validationResult); } } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { for (const tagspecId of spec.getAlsoRequiresTagWarning()) { if (!context.getTagspecsValidated().hasOwnProperty(tagspecId)) { const alsoRequiresTagspec = this.getByTagSpecId(tagspecId); @@ -3895,21 +3850,20 @@ class ParsedValidatorRules { const specUrlsByMissing = {}; for (const tagSpec of this.rules_.tags) { if (tagSpec.mandatoryAlternatives === null || - !isTagSpecCorrectHtmlFormat(tagSpec, this.htmlFormat_)) { + !amp.validator.LIGHT && !this.isTagSpecCorrectHtmlFormat_(tagSpec)) { continue; } const alternative = tagSpec.mandatoryAlternatives; if (!satisfied.hasOwnProperty(alternative)) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - missing.push(alternative); - specUrlsByMissing[alternative] = tagSpec.specUrl; - } else { + if (amp.validator.LIGHT) { validationResult.status = amp.validator.ValidationResult.Status.FAIL; return; } + missing.push(alternative); + specUrlsByMissing[alternative] = tagSpec.specUrl; } } - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { sortAndUniquify(missing); for (const tagMissing of missing) { context.addError( @@ -4099,16 +4053,16 @@ amp.validator.ValidationHandler = * @override */ markManufacturedBody() { - if (amp.validator.GENERATE_DETAILED_ERRORS) { - this.context_.addError( - amp.validator.ValidationError.Severity.ERROR, - amp.validator.ValidationError.Code.DISALLOWED_MANUFACTURED_BODY, - this.context_.getDocLocator(), - /* params */[], /* url */ '', this.validationResult_); - } else { + if (amp.validator.LIGHT) { this.validationResult_.status = amp.validator.ValidationResult.Status.FAIL; + return; } + this.context_.addError( + amp.validator.ValidationError.Severity.ERROR, + amp.validator.ValidationError.Code.DISALLOWED_MANUFACTURED_BODY, + this.context_.getDocLocator(), + /* params */[], /* url */ '', this.validationResult_); } /** @@ -4184,16 +4138,16 @@ amp.validator.ValidationHandler = validateTag(tagName, encounteredAttrs) { let tagSpecDispatch = this.rules_.dispatchForTagName(tagName); if (tagSpecDispatch === undefined) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + this.validationResult_.status = + amp.validator.ValidationResult.Status.FAIL; + } else { this.context_.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.DISALLOWED_TAG, this.context_.getDocLocator(), /* params */[tagName.toLowerCase()], /* specUrl */ '', this.validationResult_); - } else { - this.validationResult_.status = - amp.validator.ValidationResult.Status.FAIL; } return; } @@ -4242,16 +4196,16 @@ amp.validator.ValidationHandler = if (!tagSpecDispatch.hasTagSpecs()) { // TODO(gregable): Determine a good way to source a specUrl in these // instances. - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { + this.validationResult_.status = + amp.validator.ValidationResult.Status.FAIL; + } else { this.context_.addError( amp.validator.ValidationError.Severity.ERROR, amp.validator.ValidationError.Code.GENERAL_DISALLOWED_TAG, this.context_.getDocLocator(), /* params */[tagName.toLowerCase()], /* specUrl */ '', this.validationResult_); - } else { - this.validationResult_.status = - amp.validator.ValidationResult.Status.FAIL; } return; } @@ -4290,7 +4244,7 @@ amp.validator.isSeverityWarning = function(error) { * @export */ amp.validator.validateString = function(inputDocContents, opt_htmlFormat) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { throw 'not implemented'; } goog.asserts.assertString(inputDocContents, 'Input document is not a string'); @@ -4303,7 +4257,7 @@ amp.validator.validateString = function(inputDocContents, opt_htmlFormat) { return handler.Result(); }; -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** * The terminal is an abstraction for the window.console object which * accomodates differences between console implementations and provides @@ -4353,7 +4307,7 @@ if (amp.validator.GENERATE_DETAILED_ERRORS) { }; } -if (amp.validator.GENERATE_DETAILED_ERRORS) { +if (!amp.validator.LIGHT) { /** * Emits this validation result to the terminal, distinguishing warnings and * errors. @@ -4389,7 +4343,8 @@ if (amp.validator.GENERATE_DETAILED_ERRORS) { } else { errors = []; for (const error of this.errors) { - if (amp.validator.categorizeError(error) === errorCategoryFilter) { + if (('' + amp.validator.categorizeError(error)) === + errorCategoryFilter) { errors.push(error); } } @@ -4452,7 +4407,7 @@ function applyFormat(format, error) { * @export */ amp.validator.renderErrorMessage = function(error) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { throw 'not implemented'; } goog.asserts.assert(error.code !== null); @@ -4498,7 +4453,7 @@ function errorLine(filenameOrUrl, error) { * @export */ amp.validator.renderValidationResult = function(validationResult, filename) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { throw 'not implemented'; } const rendered = []; @@ -4531,7 +4486,7 @@ function isAuthorStylesheet(param) { * @export */ amp.validator.categorizeError = function(error) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { throw 'not implemented'; } // This shouldn't happen in practice. UNKNOWN_CODE would indicate that the @@ -4889,7 +4844,7 @@ amp.validator.categorizeError = function(error) { * @export */ amp.validator.annotateWithErrorCategories = function(result) { - if (!amp.validator.GENERATE_DETAILED_ERRORS) { + if (amp.validator.LIGHT) { throw 'not implemented'; } for (const error of result.errors) { @@ -4907,7 +4862,7 @@ amp.validator.annotateWithErrorCategories = function(result) { * @export */ amp.validator.validateSaxEvents = function(saxEvents, htmlFormat) { - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { throw 'not implemented'; } // TODO(powdercloud): This needs additional logic to make sure diff --git a/validator/engine/validator_test.js b/validator/engine/validator_test.js index d239092c150e..25f0058bac7d 100644 --- a/validator/engine/validator_test.js +++ b/validator/engine/validator_test.js @@ -466,8 +466,14 @@ describe('ValidatorRulesMakeSense', () => { it('tags defined', () => { expect(rules.tags.length).toBeGreaterThan(0); }); - it('attr_lists defined', () => { - expect(rules.attrLists.length).toBeGreaterThan(0); + it('direct_attr_lists defined', () => { + expect(rules.directAttrLists.length).toBeGreaterThan(0); + }); + it('global_attrs defined', () => { + expect(rules.globalAttrs.length).toBeGreaterThan(0); + }); + it('amp_layout_attrs defined', () => { + expect(rules.ampLayoutAttrs.length).toBeGreaterThan(0); }); it('min_validator_revision_required defined', () => { expect(rules.minValidatorRevisionRequired).toBeGreaterThan(0); @@ -737,18 +743,6 @@ describe('ValidatorRulesMakeSense', () => { expect(subtractDiff(allRequires, allSatisfies)).toEqual([]); }); - // attr_lists - const attrListNameIsUnique = {}; - for (const attrList of rules.attrLists) { - it('unique attr_list name', () => { - expect(attrListNameIsUnique.hasOwnProperty(attrList.name)).toBe(false); - attrListNameIsUnique[attrList.name] = 0; - }); - it('attr_list has attrs', () => { - expect(attrList.attrs.length).toBeGreaterThan(0); - }); - } - // attr_specs within rules. for (const attrSpec of rules.attrs) { attrRuleShouldMakeSense(attrSpec); diff --git a/validator/validator-main.protoascii b/validator/validator-main.protoascii index dd40109090d0..93d7a3f4054a 100644 --- a/validator/validator-main.protoascii +++ b/validator/validator-main.protoascii @@ -25,7 +25,7 @@ min_validator_revision_required: 217 # newer versions of the spec file. This is currently a Google internal # mechanism, validator.js does not use this facility. However, any # change to this file (validator-main.js) requires updating this revision id. -spec_file_revision: 374 +spec_file_revision: 379 # Validator extensions. # ===================== diff --git a/validator/validator_gen_js.py b/validator/validator_gen_js.py index 602374568ff7..2d403d73a7b6 100644 --- a/validator/validator_gen_js.py +++ b/validator/validator_gen_js.py @@ -32,6 +32,7 @@ """ import hashlib +import json import os @@ -177,8 +178,8 @@ def MessageIdForKey(self, message_key): message_id = self.message_id_by_message_key_.get(message_key, -1) if message_id != -1: return message_id - message_id = self.next_message_id_by_type_name_.get( - message_key.type_name, 0) + message_id = self.next_message_id_by_type_name_.get(message_key.type_name, + 0) self.next_message_id_by_type_name_[message_key.type_name] = message_id + 1 self.message_id_by_message_key_[message_key] = message_id return message_id @@ -270,15 +271,20 @@ def ElementTypeFor(descriptor, field_desc): field_desc.full_name in SYNTHETIC_REFERENCE_FIELD) or ( field_desc.full_name in ATTR_LIST_NAME_REFERENCE_FIELD): return 'number' - return {descriptor.FieldDescriptor.TYPE_DOUBLE: lambda: 'number', - descriptor.FieldDescriptor.TYPE_INT32: lambda: 'number', - descriptor.FieldDescriptor.TYPE_BOOL: lambda: 'boolean', - descriptor.FieldDescriptor.TYPE_STRING: lambda: 'string', - descriptor.FieldDescriptor.TYPE_ENUM: ( - lambda: field_desc.enum_type.full_name), - descriptor.FieldDescriptor.TYPE_MESSAGE: ( - lambda: field_desc.message_type.full_name)}[ - field_desc.type]() + return { + descriptor.FieldDescriptor.TYPE_DOUBLE: + lambda: 'number', + descriptor.FieldDescriptor.TYPE_INT32: + lambda: 'number', + descriptor.FieldDescriptor.TYPE_BOOL: + lambda: 'boolean', + descriptor.FieldDescriptor.TYPE_STRING: + lambda: 'string', + descriptor.FieldDescriptor.TYPE_ENUM: ( + lambda: field_desc.enum_type.full_name), + descriptor.FieldDescriptor.TYPE_MESSAGE: ( + lambda: field_desc.message_type.full_name) + }[field_desc.type]() def FieldTypeFor(descriptor, field_desc, nullable): @@ -328,25 +334,34 @@ def ValueToString(descriptor, field_desc, value): return str(value) -# For the validator-light version, skip these fields. This works by -# putting them inside a conditional with -# amp.validator.GENERATE_DETAILED_ERRORS. The Closure compiler will then -# leave them out via dead code elimination. +# For the validator-light version, skip these fields.This works by +# putting them inside a conditional with !amp.validator.LIGHT. +# The Closure compiler will then leave them out via dead code elimination. SKIP_FIELDS_FOR_LIGHT = [ - 'error_formats', 'spec_url', 'validator_revision', 'spec_file_revision', - 'template_spec_url', 'min_validator_revision_required', 'deprecation_url', - 'errors', 'unique_warning', 'also_requires_tag_warning', - 'extension_unused_unless_tag_present' + 'also_requires_tag_warning', + 'deprecation_url', + 'error_formats', + 'error_specificity', + 'errors', + 'extension_unused_unless_tag_present', + 'html_format', + 'max_bytes_spec_url', + 'min_validator_revision_required', + 'spec_file_revision', + 'spec_url', + 'template_spec_url', + 'unique_warning', + 'validator_revision', +] +SKIP_CLASSES_FOR_LIGHT = ['amp.validator.ValidationError', + 'amp.validator.ErrorFormat'] +EXPORTED_CLASSES = [ + 'amp.validator.ValidationResult', 'amp.validator.ValidationError' ] -SKIP_CLASSES_FOR_LIGHT = ['amp.validator.ValidationError'] -EXPORTED_CLASSES = ['amp.validator.ValidationResult', - 'amp.validator.ValidationError'] CONSTRUCTOR_ARG_FIELDS = [ 'amp.validator.AmpLayout.supported_layouts', 'amp.validator.AtRuleSpec.name', 'amp.validator.AtRuleSpec.type', - 'amp.validator.AttrList.attrs', - 'amp.validator.AttrList.name', 'amp.validator.AttrSpec.name', 'amp.validator.AttrTriggerSpec.also_requires_attr', 'amp.validator.BlackListedCDataRegex.error_message', @@ -357,7 +372,6 @@ def ValueToString(descriptor, field_desc, value): 'amp.validator.PropertySpecList.properties', 'amp.validator.TagSpec.tag_name', 'amp.validator.UrlSpec.allowed_protocol', - 'amp.validator.ValidatorRules.attr_lists', 'amp.validator.ValidatorRules.tags', ] @@ -373,9 +387,7 @@ def ValueToString(descriptor, field_desc, value): # In the .protoascii, some fields reference other tags by attr list name. # This is a string, and this code generator replaces these fields with attr # list ids, which are numbers. -ATTR_LIST_NAME_REFERENCE_FIELD = [ - 'amp.validator.TagSpec.attr_lists' -] +ATTR_LIST_NAME_REFERENCE_FIELD = ['amp.validator.TagSpec.attr_lists'] # These fields contain messages in the .protoascii, but we replace # them with message ids, which are numbers. Thus far we do this for @@ -388,29 +400,21 @@ def ValueToString(descriptor, field_desc, value): ] -class GenerateDetailedErrorsIf(object): +class GenerateNonLightSectionIf(object): """Wraps output lines in a condition for a light validator. For example, the code: ---------------------- - with GenerateDetailedErrorsIf(true, registry, out): + with GenerateNonLightSectionIf(true, out): out.Line('DoStuff()') ---------------------- Will generate the output: ---------------------- - if (amp.validator.GENERATE_DETAILED_ERRORS) { + if (!amp.validator.LIGHT) { DoStuff(); } ---------------------- - - Args: - descriptor: The descriptor module from the protobuf package, e.g. - google.protobuf.descriptor. - msg_desc: The descriptor for a particular message type. - out: a list of lines to output (without the newline characters) wrapped as - an OutputFormatter instance, to which this function will append. - """ def __init__(self, condition, out): @@ -427,7 +431,7 @@ def __init__(self, condition, out): def __enter__(self): if self.condition: - self.out.Line('if (amp.validator.GENERATE_DETAILED_ERRORS) {') + self.out.Line('if (!amp.validator.LIGHT) {') self.out.PushIndent(2) def __exit__(self, exception_type, value, traceback): @@ -436,7 +440,7 @@ def __exit__(self, exception_type, value, traceback): self.out.Line('}') -def PrintClassFor(descriptor, msg_desc, out): +def PrintClassFor(descriptor, msg_desc, light, out): """Prints a Javascript class for the given proto message. This method emits a Javascript class (Closure-style) for the given @@ -446,11 +450,15 @@ def PrintClassFor(descriptor, msg_desc, out): descriptor: The descriptor module from the protobuf package, e.g. google.protobuf.descriptor. msg_desc: The descriptor for a particular message type. + light: A bool indicating whether or not to generate a light validator, + that is, one which is configured to not emit detailed errors, only + supports a single html_format, and will not export the full API for + the Node.js library / tool. out: a list of lines to output (without the newline characters) wrapped as an OutputFormatter instance, to which this function will append. """ - with GenerateDetailedErrorsIf( - msg_desc.full_name in SKIP_CLASSES_FOR_LIGHT, out): + with GenerateNonLightSectionIf(msg_desc.full_name + in SKIP_CLASSES_FOR_LIGHT, out): constructor_arg_fields = [] constructor_arg_field_names = {} for field in msg_desc.fields: @@ -459,43 +467,47 @@ def PrintClassFor(descriptor, msg_desc, out): constructor_arg_field_names[field.name] = 1 out.Line('/**') for field in constructor_arg_fields: - out.Line(' * @param {%s} %s' % (FieldTypeFor(descriptor, field, - nullable=False), + out.Line(' * @param {%s} %s' % (FieldTypeFor( + descriptor, field, nullable=False), UnderscoreToCamelCase(field.name))) out.Line(' * @constructor') out.Line(' * @struct') export_or_empty = '' - if msg_desc.full_name in EXPORTED_CLASSES: + if not light and msg_desc.full_name in EXPORTED_CLASSES: out.Line(' * @export') export_or_empty = ' @export' out.Line(' */') - arguments = ','.join([UnderscoreToCamelCase(f.name) - for f in constructor_arg_fields]) + arguments = ','.join( + [UnderscoreToCamelCase(f.name) for f in constructor_arg_fields]) out.Line('%s = function(%s) {' % (msg_desc.full_name, arguments)) out.PushIndent(2) for field in msg_desc.fields: - with GenerateDetailedErrorsIf(field.name in SKIP_FIELDS_FOR_LIGHT, out): - assigned_value = 'null' - if field.name in constructor_arg_field_names: - # field.name is also the parameter name. - assigned_value = UnderscoreToCamelCase(field.name) - elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: - # ValidationResult instances may be mutated by validator.js, - # so we can't share the empty arrays. But for all other - # instances, we do share. - if msg_desc.full_name == 'amp.validator.ValidationResult': - assigned_value = '[]' - else: - assigned_value = 'EMPTY_%s_ARRAY' % ( - ElementTypeFor(descriptor, field).replace('.', '_')) - elif field.type == descriptor.FieldDescriptor.TYPE_BOOL: - assigned_value = str(field.default_value).lower() - elif field.type == descriptor.FieldDescriptor.TYPE_INT32: - assigned_value = str(field.default_value) - # TODO(johannes): Increase coverage for default values, e.g. enums. - type_name = FieldTypeFor( - descriptor, field, nullable=assigned_value == 'null') + # We generate ValidatorRules.directAttrLists, ValidatorRules.globalAttrs, + # and validator.ampLayoutAttrs instead. + if field.full_name == 'amp.validator.ValidatorRules.attr_lists': + continue + assigned_value = 'null' + if field.name in constructor_arg_field_names: + # field.name is also the parameter name. + assigned_value = UnderscoreToCamelCase(field.name) + elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: + # ValidationResult instances may be mutated by validator.js, + # so we can't share the empty arrays. But for all other + # instances, we do share. + if msg_desc.full_name == 'amp.validator.ValidationResult': + assigned_value = '[]' + else: + assigned_value = 'EMPTY_%s_ARRAY' % ( + ElementTypeFor(descriptor, field).replace('.', '_')) + elif field.type == descriptor.FieldDescriptor.TYPE_BOOL: + assigned_value = str(field.default_value).lower() + elif field.type == descriptor.FieldDescriptor.TYPE_INT32: + assigned_value = str(field.default_value) + # TODO(johannes): Increase coverage for default values, e.g. enums. + type_name = FieldTypeFor( + descriptor, field, nullable=assigned_value == 'null') + with GenerateNonLightSectionIf(field.name in SKIP_FIELDS_FOR_LIGHT, out): out.Line('/**%s @type {%s} */' % (export_or_empty, type_name)) out.Line('this.%s = %s;' % (UnderscoreToCamelCase(field.name), assigned_value)) @@ -506,36 +518,62 @@ def PrintClassFor(descriptor, msg_desc, out): out.Line('this.internedStrings = [];') out.Line('/** @type {!Array} */') out.Line('this.attrs = [];') + out.Line('/** @type {!Array>} */') + out.Line('this.directAttrLists = [];') + out.Line('/** @type {!Array} */') + out.Line('this.globalAttrs = [];') + out.Line('/** @type {!Array} */') + out.Line('this.ampLayoutAttrs = [];') out.PopIndent() out.Line('};') - out.Line('') -SKIP_ENUMS_FOR_LIGHT = ['amp.validator.ValidationError.Code', - 'amp.validator.ValidationError.Severity'] +SKIP_ENUMS_FOR_LIGHT = [ + 'amp.validator.ValidationError.Code', + 'amp.validator.ValidationError.Severity', + 'amp.validator.ErrorCategory.Code', +] -def PrintEnumFor(enum_desc, out): +def PrintEnumFor(enum_desc, light, out): """Prints a Javascript enum for the given enum descriptor. Args: enum_desc: The descriptor for a particular enum type. + light: A bool indicating whether or not to generate a light validator, + that is, one which is configured to not emit detailed errors, only + supports a single html_format, and will not export the full API for + the Node.js library / tool. out: a list of lines to output (without the newline characters) wrapped as an OutputFormatter instance, to which this function will append. """ - with GenerateDetailedErrorsIf( - enum_desc.full_name in SKIP_ENUMS_FOR_LIGHT, out): + with GenerateNonLightSectionIf(enum_desc.full_name + in SKIP_ENUMS_FOR_LIGHT, out): out.Line('/**') - out.Line(' * @enum {string}') - out.Line(' * @export') + if light: + out.Line(' * @enum {number}') + else: + out.Line(' * @enum {string}') + out.Line(' * @export') out.Line(' */') out.Line('%s = {' % enum_desc.full_name) out.PushIndent(2) + names = [] for v in enum_desc.values: - out.Line("%s: '%s'," % (v.name, v.name)) + names.append('%s' % v.name) + if light: + out.Line('%s: %d,' % (v.name, v.number)) + else: + out.Line("%s: '%s'," % (v.name, v.name)) out.PopIndent() out.Line('};') - out.Line('') + out.Line('/** @type {!Array} */') + out.Line('%s_NamesByIndex = ["%s"];' % (enum_desc.full_name, + '","'.join(names))) + out.Line('/** @type {!Array} */' % enum_desc.full_name) + out.Line('%s_ValuesByIndex = [%s];' % ( + enum_desc.full_name, ','.join( + ['%s.%s' % (enum_desc.full_name, n)for n in names]))) def TagSpecName(tag_spec): @@ -555,7 +593,7 @@ def TagSpecName(tag_spec): return tag_spec.tag_name.lower() -def MaybePrintMessageValue(descriptor, field_val, registry, out): +def MaybePrintMessageValue(descriptor, field_val, registry, light, out): """Print field_val if necessary, and return its message reference. Args: @@ -564,6 +602,10 @@ def MaybePrintMessageValue(descriptor, field_val, registry, out): field_val: The value of a field, a proto message. registry: an instance of MessageRegistry, used for mapping from messages to message keys. + light: A bool indicating whether or not to generate a light validator, + that is, one which is configured to not emit detailed errors, only + supports a single html_format, and will not export the full API for + the Node.js library / tool. out: a list of lines to output (without the newline characters) wrapped as an OutputFormatter instance, to which this function will append. Returns: @@ -572,7 +614,7 @@ def MaybePrintMessageValue(descriptor, field_val, registry, out): """ message_key = MessageKey(field_val) if not registry.IsPrinted(message_key): - PrintObject(descriptor, field_val, registry, out) + PrintObject(descriptor, field_val, registry, light, out) return registry.MessageReferenceForKey(message_key) @@ -588,7 +630,7 @@ def IsTrivialAttrSpec(attr): attr.HasField('name') and len(attr.ListFields()) == 1) -def AssignedValueFor(descriptor, field_desc, field_val, registry, out): +def AssignedValueFor(descriptor, field_desc, field_val, registry, light, out): """Helper function for PrintObject: computes / assigns a value for a field. Note that if the field is a complex field (a message), this function @@ -601,6 +643,10 @@ def AssignedValueFor(descriptor, field_desc, field_val, registry, out): field_val: The value for a particular field. registry: an instance of MessageRegistry, used for mapping from messages to message keys. + light: A bool indicating whether or not to generate a light validator, + that is, one which is configured to not emit detailed errors, only + supports a single html_format, and will not export the full API for + the Node.js library / tool. out: a list of lines to output (without the newline characters) wrapped as an OutputFormatter instance, to which this function will append. Returns: @@ -614,19 +660,20 @@ def AssignedValueFor(descriptor, field_desc, field_val, registry, out): elif field_desc.full_name in ATTR_LIST_NAME_REFERENCE_FIELD: render_value = lambda v: str(registry.MessageIdForAttrListName(v)) elif field_desc.full_name in SYNTHETIC_REFERENCE_FIELD: + def InternOrReference(value): if field_desc.type == descriptor.FieldDescriptor.TYPE_STRING: return str(registry.InternString(value)) if IsTrivialAttrSpec(value): return str(registry.InternString(value.name)) return str(registry.MessageIdForKey(MessageKey(value))) + render_value = InternOrReference elif field_desc.type == descriptor.FieldDescriptor.TYPE_MESSAGE: render_value = ( - lambda v: MaybePrintMessageValue(descriptor, v, registry, out)) + lambda v: MaybePrintMessageValue(descriptor, v, registry, light, out)) else: - render_value = ( - lambda v: ValueToString(descriptor, field_desc, v)) # pylint: disable=cell-var-from-loop + render_value = (lambda v: ValueToString(descriptor, field_desc, v)) # pylint: disable=cell-var-from-loop # Then we iterate over the field if it's repeated, or else just # call the render function once. @@ -636,7 +683,7 @@ def InternOrReference(value): return render_value(field_val) -def PrintObject(descriptor, msg, registry, out): +def PrintObject(descriptor, msg, registry, light, out): """Prints an object, by recursively constructing it. This routine emits Javascript which will construct an object modeling @@ -649,6 +696,10 @@ def PrintObject(descriptor, msg, registry, out): msg: A protocol message instance. registry: an instance of MessageRegistry, used for mapping from messages to message keys. + light: A bool indicating whether or not to generate a light validator, + that is, one which is configured to not emit detailed errors, only + supports a single html_format, and will not export the full API for + the Node.js library / tool. out: a list of lines to output (without the newline characters) wrapped as an OutputFormatter instance, to which this function will append. Returns: @@ -660,28 +711,34 @@ def PrintObject(descriptor, msg, registry, out): field_and_assigned_values = [] for (field_desc, field_val) in msg.ListFields(): - field_and_assigned_values.append( - (field_desc, AssignedValueFor( - descriptor, field_desc, field_val, registry, out))) + # We generate ValidatorRules.directAttrLists, ValidatorRules.globalAttrs, + # and validator.ampLayoutAttrs instead. + if field_desc.full_name == 'amp.validator.ValidatorRules.attr_lists': + continue + if light and field_desc.name in SKIP_FIELDS_FOR_LIGHT: + continue + field_and_assigned_values.append((field_desc, AssignedValueFor( + descriptor, field_desc, field_val, registry, light, out))) # First we emit the constructor call, with the appropriate arguments. - constructor_arg_values = [value - for (field, value) in field_and_assigned_values - if field.full_name in CONSTRUCTOR_ARG_FIELDS] + constructor_arg_values = [ + value for (field, value) in field_and_assigned_values + if field.full_name in CONSTRUCTOR_ARG_FIELDS + ] - this_message_reference = registry.MessageReferenceForKey( - this_message_key) - out.Line('var %s = new %s(%s);' % ( - this_message_reference, msg.DESCRIPTOR.full_name, - ','.join(constructor_arg_values))) + this_message_reference = registry.MessageReferenceForKey(this_message_key) + out.Line('var %s = new %s(%s);' % + (this_message_reference, msg.DESCRIPTOR.full_name, + ','.join(constructor_arg_values))) # Then we emit the remaining field values as assignments. for (field, value) in field_and_assigned_values: - if field.full_name not in CONSTRUCTOR_ARG_FIELDS: - with GenerateDetailedErrorsIf(field.name in SKIP_FIELDS_FOR_LIGHT, out): - out.Line('%s.%s = %s;' % - (this_message_reference, UnderscoreToCamelCase(field.name), - value)) + if light and field.name in SKIP_FIELDS_FOR_LIGHT: + continue + if field.full_name in CONSTRUCTOR_ARG_FIELDS: + continue + out.Line('%s.%s = %s;' % (this_message_reference, + UnderscoreToCamelCase(field.name), value)) def DispatchKeyForTagSpecOrNone(tag_spec): @@ -703,8 +760,8 @@ def DispatchKeyForTagSpecOrNone(tag_spec): return None -def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, - descriptor, out): +def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, light, + html_format, descriptor, out): """Main method for the code generator. This method reads the specfile and emits Javascript to sys.stdout. @@ -715,11 +772,24 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, validator_pb2: The proto2 Python module generated from validator.proto. text_format: The text_format module from the protobuf package, e.g. google.protobuf.text_format. + light: If true, then no detailed errors will be emitted by the validator, + and the rules will be pre-filtered for html_format. + html_format: Either a TagSpec.HtmlFormat enum value indicating which + HTML format the generated validator code should support, + or None indicating that all formats should be supported. descriptor: The descriptor module from the protobuf package, e.g. google.protobuf.descriptor. out: a list of lines to output (without the newline characters), to which this function will append. """ + + if light: + # If we generate a light validator, we require that the rules be filtered + # for a specific format (in practice thus far 'AMP' or 'AMP4ADS'). + assert html_format is not None + else: + assert html_format is None + # First, find the descriptors and enums and generate Javascript # classes and enums. msg_desc_by_name = {} @@ -737,13 +807,16 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, out.Line('') for name in all_names: out.Line("goog.provide('%s');" % name) - out.Line("goog.provide('amp.validator.GENERATE_DETAILED_ERRORS');") + out.Line("goog.provide('amp.validator.LIGHT');") out.Line("goog.provide('amp.validator.VALIDATE_CSS');") out.Line("goog.provide('amp.validator.createRules');") out.Line('') out.Line('/** @define {boolean} */') - out.Line('amp.validator.GENERATE_DETAILED_ERRORS = true;') + if light: + out.Line('amp.validator.LIGHT = true;') + else: + out.Line('amp.validator.LIGHT = false;') out.Line('') out.Line('/** @define {boolean} */') out.Line('amp.validator.VALIDATE_CSS = true;') @@ -754,25 +827,35 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, # happy, we use one empty array per element type. # PS: It may also help execution performance in V8 to keep the element types # separate but we did not verify that. - empty_arrays = [name for name in all_names - if name in msg_desc_by_name or name in enum_desc_by_name] - empty_arrays += ['string', 'number', 'boolean'] - for name in empty_arrays: + all_type_names = ['string', 'number', 'boolean'] + [ + n for n in all_names if n in msg_desc_by_name] + [ + n for n in all_names if n in enum_desc_by_name] + + for name in all_type_names: out.Line('/** @type {!Array} */' % name) out.Line('var EMPTY_%s_ARRAY = [];' % name.replace('.', '_')) out.Line('') for name in all_names: if name in msg_desc_by_name: - PrintClassFor(descriptor, msg_desc_by_name[name], out) + PrintClassFor(descriptor, msg_desc_by_name[name], light, out) elif name in enum_desc_by_name: - PrintEnumFor(enum_desc_by_name[name], out) + PrintEnumFor(enum_desc_by_name[name], light, out) # Read the rules file, validator.protoascii by parsing it as a text # message of type ValidatorRules. rules = validator_pb2.ValidatorRules() text_format.Merge(open(specfile).read(), rules) + # If html_format is set, only keep the tags which are relevant to it. + if html_format is not None: + filtered_rules = [ + t for t in rules.tags + if not t.html_format or html_format in t.html_format + ] + del rules.tags[:] + rules.tags.extend(filtered_rules) + registry = MessageRegistry() # Register the tagspecs so they have ids 0 - rules.tags.length. This means @@ -790,7 +873,7 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, out.Line(' */') out.Line('amp.validator.createRules = function() {') out.PushIndent(2) - PrintObject(descriptor, rules, registry, out) + PrintObject(descriptor, rules, registry, light, out) # We use this below to reference the variable holding the rules instance. rules_reference = registry.MessageReferenceForKey(MessageKey(rules)) @@ -801,11 +884,8 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, tag_spec_id = registry.MessageIdForTagSpecName(TagSpecName(tag_spec)) dispatch_key = DispatchKeyForTagSpecOrNone(tag_spec) if dispatch_key: - out.Line('%s.dispatchKeyByTagSpecId[%d]="%s";' % ( - rules_reference, tag_spec_id, dispatch_key)) - - out.Line('%s.internedStrings = ["%s"];' % ( - rules_reference, '","'.join(registry.InternedStrings()))) + out.Line('%s.dispatchKeyByTagSpecId[%d]="%s";' % + (rules_reference, tag_spec_id, dispatch_key)) # Create a mapping from attr spec ids to AttrSpec instances, deduping the # AttrSpecs. Then sort by these ids, so now we get a dense array starting @@ -820,10 +900,47 @@ def GenerateValidatorGeneratedJs(specfile, validator_pb2, text_format, # Emit the attr specs, then assign a list of references to them to # Rules.attrs. for attr in sorted_attrs: - PrintObject(descriptor, attr, registry, out) - out.Line('%s.attrs = [%s];' % (rules_reference, ','.join([ - registry.MessageReferenceForKey(MessageKey(a)) - for a in sorted_attrs]))) + PrintObject(descriptor, attr, registry, light, out) + out.Line('%s.attrs = [%s];' % (rules_reference, ','.join( + [registry.MessageReferenceForKey(MessageKey(a)) for a in sorted_attrs]))) + + # We emit the attr lists as arrays of arrays of numbers (which are + # the attr ids), and treat the globalAttrs and the ampLayoutAttrs + # seperately for fast access. + direct_attr_lists = [] + global_attrs = [] + amp_layout_attrs = [] + unique_attr_list_names = set() + for attr_list in rules.attr_lists: + assert attr_list.name not in unique_attr_list_names, attr_list.name + unique_attr_list_names.add(attr_list.name) + assert attr_list.attrs + + attr_id_list = [] + for attr in attr_list.attrs: + if IsTrivialAttrSpec(attr): + attr_id_list.append(registry.InternString(attr.name)) + else: + attr_id_list.append(registry.MessageIdForKey(MessageKey(attr))) + if attr_list.name == '$GLOBAL_ATTRS': + global_attrs = attr_id_list + direct_attr_lists.append([]) + elif attr_list.name == '$AMP_LAYOUT_ATTRS': + amp_layout_attrs = attr_id_list + direct_attr_lists.append([]) + else: + direct_attr_lists.append(attr_id_list) + + out.Line('%s.directAttrLists = %s;' % ( + rules_reference, json.dumps(direct_attr_lists))) + out.Line('%s.globalAttrs = %s;' % ( + rules_reference, json.dumps(global_attrs))) + out.Line('%s.ampLayoutAttrs = %s;' % ( + rules_reference, json.dumps(amp_layout_attrs))) + + # We emit these after the last call to registry.InternString. + out.Line('%s.internedStrings = ["%s"];' % + (rules_reference, '","'.join(registry.InternedStrings()))) out.Line('return %s;' % rules_reference) out.PopIndent()