fileoverview import { HintContext } from 'hint/dist/src/lib/hint-context' ; import { IHint } from 'hint/dist/src/lib/types' ; import { debug as d } from '@hint/utils-debug' ; import { HTMLElement } from '@hint/utils-dom' ; import { Severity } from '@hint/utils-types' ; import { StyleEvents , StyleParse } from '@hint/parser-css' ; import { getCSSCodeSnippet , getCSSLocationFromNode } from '@hint/utils-css' ; import meta from './meta' ; import { getMessage } from './i18n.import' ; const debug = d ( __filename ) ; const findParentSVGElement = ( element ) : HTMLElement | null => { if ( ! element . parentElement ) { return null ; } if ( element . parentElement . nodeName === 'svg' ) { return element . parentElement ; } return findParentSVGElement ( element . parentElement ) ; } ; const isOutsideParentSVG = ( parentSVG ) => { return ( element ) : boolean => { const elementsParentSVG = findParentSVGElement ( element ) ; if ( ! elementsParentSVG ) { return true ; } if ( ! elementsParentSVG . isSame ( parentSVG ) ) { return true ; } return false ; } ; } ; export default class ScopedSvgStylesHint implements IHint { public static readonly meta = meta ; public constructor ( context ) { /** Generate a report message from elements matched outside the SVG. */ const formatRuleMessage = ( numberOfElementsOutsideSVG ) : string => { return getMessage ( 'reportRuleImpacting' , context . language , [ `${ numberOfElementsOutsideSVG }` ] ) ; } ; const formatElementMessage = ( codeSnippet ) : string => { return getMessage ( 'reportImpactedElement' , context . language , [ codeSnippet ] ) ; } ; const validateStyle = ( { ast , element , resource } ) => { if ( ! element ) { return ; } const parentSVG = findParentSVGElement ( element ) ; if ( ! parentSVG ) { return ; } debug ( 'Validating hint scoped-svg-styles' ) ; ast . walkRules ( ( rule ) => { const selectors = rule . selectors ; for ( const selector of selectors ) { const matchingElements = element . ownerDocument . querySelectorAll ( selector ) ; const matchingElementsOutsideParentSVG = matchingElements . filter ( isOutsideParentSVG ( parentSVG ) ) ; if ( matchingElementsOutsideParentSVG . length ) { const message = formatRuleMessage ( matchingElementsOutsideParentSVG . length ) ; const location = getCSSLocationFromNode ( rule ) ; const codeSnippet = getCSSCodeSnippet ( rule ) ; context . report ( resource , message , { codeLanguage : 'css' , codeSnippet , element , location , severity : Severity . error } ) ; let maxReportsPerCSSRule = Infinity ; if ( context . hintOptions && context . hintOptions . maxReportsPerCSSRule !== undefined ) { maxReportsPerCSSRule = context . hintOptions . maxReportsPerCSSRule ; } for ( let i = 0 ; ( i < matchingElementsOutsideParentSVG . length && i < maxReportsPerCSSRule ) ; i ++ ) { context . report ( resource , formatElementMessage ( codeSnippet ) , { element : matchingElementsOutsideParentSVG [ i ] , severity : Severity . error } ) ; } } } } ) ; } ; context . on ( 'parse::end::css' , validateStyle ) ; } }