@@ -35,6 +35,7 @@ goog.require('amp.validator.AttrSpec');
3535goog . require ( 'amp.validator.CdataSpec' ) ;
3636goog . require ( 'amp.validator.CssDeclaration' ) ;
3737goog . require ( 'amp.validator.CssSpec' ) ;
38+ goog . require ( 'amp.validator.ErrorCategory' ) ;
3839goog . require ( 'amp.validator.ExtensionSpec' ) ;
3940goog . require ( 'amp.validator.PropertySpecList' ) ;
4041goog . require ( 'amp.validator.ReferencePoint' ) ;
@@ -535,6 +536,42 @@ class ParsedAttrSpecs {
535536 }
536537}
537538
539+ /** @enum {string} */
540+ const RecordValidated = {
541+ ALWAYS : 'ALWAYS' ,
542+ NEVER : 'NEVER' ,
543+ IF_PASSING : 'IF_PASSING'
544+ } ;
545+
546+ /**
547+ * We only track (that is, add them to Context.RecordTagspecValidated) validated
548+ * tagspecs as necessary. That is, if it's needed for document scope validation:
549+ * - Mandatory tags
550+ * - Unique tags
551+ * - Tags (identified by their TagSpecName() that are required by other tags.
552+ * @param {!amp.validator.TagSpec } tag
553+ * @param {number } tagSpecId
554+ * @param {!Array<boolean> } tagSpecIdsToTrack
555+ * @return {!RecordValidated }
556+ */
557+ function shouldRecordTagspecValidated ( tag , tagSpecId , tagSpecIdsToTrack ) {
558+ // Always update from TagSpec if the tag is passing. If it's failing we
559+ // typically want to update from the best match as it can satisfy
560+ // requirements which otherwise can confuse the user later. The exception is
561+ // tagspecs which introduce requirements but satisfy none, such as unique.
562+ // https://github.com/ampproject/amphtml/issues/24359
563+
564+ // Mandatory and tagSpecIdsToTrack only satisfy requirements, making the
565+ // output less verbose even if the tag is failing.
566+ if ( tag . mandatory || tagSpecIdsToTrack . hasOwnProperty ( tagSpecId ) )
567+ return RecordValidated . ALWAYS ;
568+ // Unique and similar can introduce requirements, ie: there cannot be another
569+ // such tag. We don't want to introduce requirements for failing tags.
570+ if ( tag . unique || tag . requires . length > 0 || tag . uniqueWarning )
571+ return RecordValidated . IF_PASSING ;
572+ return RecordValidated . NEVER ;
573+ }
574+
538575/**
539576 * This wrapper class provides access to a TagSpec and a tag id
540577 * which is unique within its context, the ParsedValidatorRules.
@@ -543,7 +580,7 @@ class ParsedAttrSpecs {
543580class ParsedTagSpec {
544581 /**
545582 * @param {!ParsedAttrSpecs } parsedAttrSpecs
546- * @param {boolean } shouldRecordTagspecValidated
583+ * @param {!RecordValidated } shouldRecordTagspecValidated
547584 * @param {!amp.validator.TagSpec } tagSpec
548585 * @param {number } id
549586 */
@@ -574,7 +611,7 @@ class ParsedTagSpec {
574611 */
575612 this . isTypeJson_ = false ;
576613 /**
577- * @type {boolean }
614+ * @type {!RecordValidated }
578615 * @private
579616 */
580617 this . shouldRecordTagspecValidated_ = shouldRecordTagspecValidated ;
@@ -816,7 +853,7 @@ class ParsedTagSpec {
816853 * Context.recordTagspecValidated_ if it was validated
817854 * successfullly. For performance, this is only done for tags that are
818855 * mandatory, unique, or possibly required by some other tag.
819- * @return {boolean }
856+ * @return {!RecordValidated }
820857 */
821858 shouldRecordTagspecValidated ( ) {
822859 return this . shouldRecordTagspecValidated_ ;
@@ -2515,6 +2552,9 @@ class Context {
25152552 updateFromTagResult_ ( result ) {
25162553 if ( result . bestMatchTagSpec === null ) { return ; }
25172554 const parsedTagSpec = result . bestMatchTagSpec ;
2555+ const isPassing =
2556+ ( result . validationResult . status ===
2557+ amp . validator . ValidationResult . Status . PASS ) ;
25182558
25192559 this . extensions_ . updateFromTagResult ( result ) ;
25202560 // If this requires an extension and we are still in the document head,
@@ -2530,7 +2570,7 @@ class Context {
25302570 // the document.
25312571 this . satisfyConditionsFromTagSpec_ ( parsedTagSpec ) ;
25322572 this . satisfyMandatoryAlternativesFromTagSpec_ ( parsedTagSpec ) ;
2533- this . recordValidatedFromTagSpec_ ( parsedTagSpec ) ;
2573+ this . recordValidatedFromTagSpec_ ( isPassing , parsedTagSpec ) ;
25342574
25352575 const { validationResult} = result ;
25362576 for ( const provision of validationResult . valueSetProvisions )
@@ -2546,8 +2586,7 @@ class Context {
25462586 errors . push ( requirement . errorIfUnsatisfied ) ;
25472587 }
25482588
2549- if ( result . validationResult . status ===
2550- amp . validator . ValidationResult . Status . PASS ) {
2589+ if ( isPassing ) {
25512590 // If the tag spec didn't match, we don't know that the tag actually
25522591 // contained a URL, so no need to complain about it.
25532592 this . markUrlSeenFromMatchingTagSpec_ ( parsedTagSpec ) ;
@@ -2665,12 +2704,17 @@ class Context {
26652704
26662705 /**
26672706 * Records that this document contains a tag matching a particular tag spec.
2707+ * @param {boolean } isPassing
26682708 * @param {!ParsedTagSpec } parsedTagSpec
26692709 * @private
26702710 */
2671- recordValidatedFromTagSpec_ ( parsedTagSpec ) {
2672- if ( parsedTagSpec . shouldRecordTagspecValidated ( ) )
2711+ recordValidatedFromTagSpec_ ( isPassing , parsedTagSpec ) {
2712+ const recordValidated = parsedTagSpec . shouldRecordTagspecValidated ( ) ;
2713+ if ( recordValidated == RecordValidated . ALWAYS ) {
2714+ this . tagspecsValidated_ [ parsedTagSpec . id ( ) ] = true ;
2715+ } else if ( isPassing && ( recordValidated == RecordValidated . IF_PASSING ) ) {
26732716 this . tagspecsValidated_ [ parsedTagSpec . id ( ) ] = true ;
2717+ }
26742718 }
26752719
26762720 /**
@@ -3478,21 +3522,6 @@ function CalculateLayout(inputLayout, width, height, sizesAttr, heightsAttr) {
34783522 }
34793523}
34803524
3481- /**
3482- * We only track (that is, add them to Context.RecordTagspecValidated) validated
3483- * tagspecs as necessary. That is, if it's needed for document scope validation:
3484- * - Mandatory tags
3485- * - Unique tags
3486- * - Tags (identified by their TagSpecName() that are required by other tags.
3487- * @param {!amp.validator.TagSpec } tag
3488- * @param {number } tagSpecId
3489- * @param {!Array<boolean> } tagSpecIdsToTrack
3490- * @return {boolean }
3491- */
3492- function shouldRecordTagspecValidated ( tag , tagSpecId , tagSpecIdsToTrack ) {
3493- return tag . mandatory || tag . unique || tag . requires . length > 0 ||
3494- tagSpecIdsToTrack . hasOwnProperty ( tagSpecId ) || tag . uniqueWarning ;
3495- }
34963525
34973526/**
34983527 * DispatchKey represents a tuple of either 1-3 strings:
@@ -6238,3 +6267,28 @@ amp.validator.renderValidationResult = function(validationResult, filename) {
62386267function isAuthorStylesheet ( param ) {
62396268 return goog . string . /*OK*/ startsWith ( param , 'style amp-custom' ) ;
62406269}
6270+
6271+ /**
6272+ * This function was removed in October 2019. Older versions of the nodejs
6273+ * amphtml-validator library still call this function, so this stub is left
6274+ * in place for now so as not to break them. TODO(#25188): Delete this function
6275+ * after most usage had moved to a newer version of the amphtml-validator lib.
6276+ * @param {!amp.validator.ValidationError } error
6277+ * @return {!amp.validator.ErrorCategory.Code }
6278+ * @export
6279+ */
6280+ amp . validator . categorizeError = function ( error ) {
6281+ return amp . validator . ErrorCategory . Code . UNKNOWN ;
6282+ } ;
6283+
6284+ /**
6285+ * Convenience function which calls |CategorizeError| for each error
6286+ * in |result| and sets its category field accordingly.
6287+ * @param {!amp.validator.ValidationResult } result
6288+ * @export
6289+ */
6290+ amp . validator . annotateWithErrorCategories = function ( result ) {
6291+ for ( const error of result . errors ) {
6292+ error . category = amp . validator . categorizeError ( error ) ;
6293+ }
6294+ } ;
0 commit comments