@angular-eslint/eslint-plugin-template 12.0.0-alpha.2 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,19 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [12.0.0](https://github.com/angular-eslint/angular-eslint/compare/v4.3.0...v12.0.0) (2021-05-13)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **template-parser:** add missing `Conditional` and its keys to `VisitorKeys` ([#445](https://github.com/angular-eslint/angular-eslint/issues/445)) ([5ad0f1a](https://github.com/angular-eslint/angular-eslint/commit/5ad0f1aeca244dbd27496e5a2d8c569994a24dcf))
11
+
12
+ ### Features
13
+
14
+ - update tslint-to-eslint-config to 2.4.0 ([7352ad2](https://github.com/angular-eslint/angular-eslint/commit/7352ad260644952abebf06773703f7b550d870fb))
15
+ - **eslint-plugin-template:** add rule eqeqeq ([#444](https://github.com/angular-eslint/angular-eslint/issues/444)) ([e15148c](https://github.com/angular-eslint/angular-eslint/commit/e15148cc31d54641815d08f97f14b3388d8dcde2))
16
+ - update eslint to ^7.26.0, [@typescript-eslint](https://github.com/typescript-eslint) to 4.23.0 ([9e31c38](https://github.com/angular-eslint/angular-eslint/commit/9e31c3881a13d6ce3b642b9c23c67e2e0f2d1aa1))
17
+ - update to angular v12 ([c80008d](https://github.com/angular-eslint/angular-eslint/commit/c80008df8f6b9d08daf3043dffc1be45f8cfbe81))
18
+
6
19
  # [4.3.0](https://github.com/angular-eslint/angular-eslint/compare/v4.2.1...v4.3.0) (2021-05-12)
7
20
 
8
21
  ### Features
@@ -11,6 +11,7 @@
11
11
  "@angular-eslint/template/click-events-have-key-events": "error",
12
12
  "@angular-eslint/template/conditional-complexity": "error",
13
13
  "@angular-eslint/template/cyclomatic-complexity": "error",
14
+ "@angular-eslint/template/eqeqeq": "error",
14
15
  "@angular-eslint/template/i18n": "error",
15
16
  "@angular-eslint/template/mouse-events-have-key-events": "error",
16
17
  "@angular-eslint/template/no-any": "error",
@@ -2,6 +2,7 @@
2
2
  "extends": "./configs/base.json",
3
3
  "rules": {
4
4
  "@angular-eslint/template/banana-in-box": "error",
5
+ "@angular-eslint/template/eqeqeq": "error",
5
6
  "@angular-eslint/template/no-negated-async": "error"
6
7
  }
7
8
  }
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ declare const _default: {
13
13
  "@angular-eslint/template/click-events-have-key-events": string;
14
14
  "@angular-eslint/template/conditional-complexity": string;
15
15
  "@angular-eslint/template/cyclomatic-complexity": string;
16
+ "@angular-eslint/template/eqeqeq": string;
16
17
  "@angular-eslint/template/i18n": string;
17
18
  "@angular-eslint/template/mouse-events-have-key-events": string;
18
19
  "@angular-eslint/template/no-any": string;
@@ -78,6 +79,9 @@ declare const _default: {
78
79
  "cyclomatic-complexity": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"cyclomaticComplexity", [{
79
80
  maxComplexity: number;
80
81
  }], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
82
+ eqeqeq: import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"eqeqeq", [{
83
+ readonly allowNullOrUndefined?: boolean | undefined;
84
+ }], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
81
85
  i18n: import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<import("./rules/i18n").MessageIds, [{
82
86
  readonly boundTextAllowedPattern?: string | undefined;
83
87
  readonly checkId?: boolean | undefined;
@@ -94,7 +98,7 @@ declare const _default: {
94
98
  "no-duplicate-attributes": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"noDuplicateAttributes", [{
95
99
  allowTwoWayDataBinding?: boolean | undefined;
96
100
  }], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
97
- "no-negated-async": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<import("./rules/no-negated-async").MessageIds, [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
101
+ "no-negated-async": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"noNegatedAsync", [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
98
102
  "no-positive-tabindex": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"noPositiveTabindex", [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
99
103
  "use-track-by-function": import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"useTrackByFunction", [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
100
104
  };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var e=require("typescript"),t=require("@typescript-eslint/experimental-utils"),n=require("@angular/compiler"),a=require("aria-query");function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=o(e);function r(e,t){const n=(e=e.replace(/\r\n/g,"\n")).indexOf(t);return[n,n+t.length]}const i=new Map;var c={"extract-inline-html":{preprocess:function(e,t){const n=[e];if(!function(e,t){return!![".component.ts",".page.ts",".dialog.ts",".modal.ts",".popover.ts",".bottomsheet.ts",".snackbar.ts"].some(e=>t.endsWith(e))||!(!e.includes("Component")||!e.includes("@angular/core"))}(e,t))return n;try{const a=s.default.createSourceFile(t,e,s.default.ScriptTarget.Latest,!0),o=a.statements.filter(e=>s.default.isClassDeclaration(e));if(!o||!o.length)return n;const c=[];for(const e of o)if(e.decorators)for(const t of e.decorators)s.default.isCallExpression(t.expression)&&s.default.isIdentifier(t.expression.expression)&&"Component"===t.expression.expression.text&&c.push(t);if(!c||!c.length)return n;const l=[e];let u=0;for(const t of c){if(!s.default.isDecorator(t)||!s.default.isCallExpression(t.expression)||1!==t.expression.arguments.length)continue;const n=t.expression.arguments[0];if(!s.default.isObjectLiteralExpression(n))continue;const o=n.properties.find(e=>e&&e.name&&"template"===e.name.getText());if(n.properties.find(e=>e&&e.name&&"templateUrl"===e.name.getText())||!o)continue;if(!s.default.isPropertyAssignment(o)||!s.default.isStringLiteralLike(o.initializer))continue;const c=o.initializer.text,m=r(e,c),p=`inline-template-${++u}.component.html`;i.set(p,{range:m,lineAndCharacter:{start:a.getLineAndCharacterOfPosition(m[0]),end:a.getLineAndCharacterOfPosition(m[1])}}),l.push({text:c,filename:p})}return l}catch(e){return console.log(e),console.error("preprocess: ERROR could not parse @Component() metadata",t),n}},postprocess:function(e,t){const n=e[0];if(1===e.length)return n;const a=e.slice(1);return[...n,...[].concat(...a.map((e,t)=>{const n=`inline-template-${++t}.component.html`,a=i.get(n);return a?e.map(e=>{if(e.line=e.line+a.lineAndCharacter.start.line,e.endLine=e.endLine+a.lineAndCharacter.start.line,e.fix){const t=a.range[0];e.fix.range=[t+e.fix.range[0],t+e.fix.range[1]]}return e}):[]}))]},supportsAutofix:!0}};const l=t.ESLintUtils.RuleCreator(e=>"https://github.com/angular-eslint/angular-eslint");function u(e){return m(e),e.parserServices}function m(e){var t,n;if(null==(t=e.parserServices)||!t.convertNodeSourceSpanToLoc||null==(n=e.parserServices)||!n.convertElementSourceSpanToLoc)throw new Error("You have used a rule which requires '@angular-eslint/template-parser' to be used as the 'parser' in your ESLint config.")}const p=Symbol("PROPERTY");function d(e,t){const a=e.attributes.find(e=>e.name===t);if(a)return a.value;const o=e.inputs.find(e=>e.name===t);return o&&o.value instanceof n.ASTWithSource?o.value.ast instanceof n.LiteralPrimitive?o.value.ast.value:p:null}var g=l({name:"accessibility-alt-text",meta:{type:"suggestion",docs:{description:"Enforces alternate text for elements which require the alt, aria-label, aria-labelledby attributes.",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityAltText:"<{{element}}/> element must have a text alternative."}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(img|area|object|input)$/]"(n){if(!function(e){return"img"===e.name?function(e){return e.attributes.some(({name:e})=>b(e))||e.inputs.some(({name:e})=>b(e))}(e):"object"===e.name?function(e){let t=!1,n=!1;for(const a of e.attributes)t="title"===a.name,n=y(a.name);if(t||n)return!0;let a=!1,o=!1;for(const t of e.inputs)a="title"===t.name,o=y(t.name);return!(!a&&!o)||e.children.length>0&&!!e.children[0].value}(e):"area"===e.name?f(e):function(e){return"image"!==d(e,"type")||f(e)}(e)}(n)){const a=t.convertElementSourceSpanToLoc(e,n);e.report({loc:a,messageId:"accessibilityAltText",data:{element:n.name}})}}}}});function f(e){let t=!1,n=!1;for(const a of e.attributes)t=b(a.name),n=y(a.name);if(t||n)return!0;let a=!1,o=!1;for(const t of e.inputs)a=b(t.name),o=y(t.name);return a||o}function y(e){return"aria-label"===e||"aria-labelledby"===e}function b(e){return"alt"===e}function h(e){if("string"==typeof e){if("true"===e)return!0;if("false"===e)return!1}return e}function x(e){if("INPUT"===e.name.toUpperCase()){const t=d(e,"type");if("string"==typeof t&&"HIDDEN"===t.toUpperCase())return!0}const t=h(d(e,"aria-hidden"));return""===t||t===p||!0===t}const v=new Set(["aria-label","innerHtml","innerHTML","innerText","outerHTML","title"]);var S=l({name:"accessibility-elements-content",meta:{type:"suggestion",docs:{description:"Ensures that the heading, anchor and button elements have content in it",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityElementsContent:"<{{element}}> should have content"}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(a|button|h1|h2|h3|h4|h5|h6)$/][children.length=0]"(n){if(x(n))return;const{attributes:a,inputs:o,name:s,sourceSpan:r}=n;if([...a,...o].map(({name:e})=>e).some(e=>v.has(e)))return;const i=t.convertNodeSourceSpanToLoc(r);e.report({loc:i,messageId:"accessibilityElementsContent",data:{element:s}})}}}});function A(e,t){return function e({children:a}){return a.some(a=>a instanceof n.TmplAstElement&&(a.name===t||e(a)))}(e)}const T={items:{type:"string"},type:"array",uniqueItems:!0},C=["button","input","meter","output","progress","select","textarea"],I=["for","htmlFor"],E=["label"];var B=l({name:"accessibility-label-for",meta:{deprecated:!0,replacedBy:["accessibility-label-has-associated-control"],type:"suggestion",docs:{description:"Ensures that a label element/component is associated with a form element",category:"Best Practices",recommended:!1},schema:[{additionalProperties:!1,properties:{controlComponents:T,labelAttributes:T,labelComponents:T},type:"object"}],messages:{accessibilityLabelFor:"A label element/component must be associated with a form element"}},defaultOptions:[{controlComponents:C,labelAttributes:I,labelComponents:E}],create(e,[t]){const n=u(e),{controlComponents:a,labelAttributes:o,labelComponents:s}=function({controlComponents:e,labelAttributes:t,labelComponents:n}){return{controlComponents:new Set([...C,...null!=e?e:[]]),labelAttributes:new Set([...I,...null!=t?t:[]]),labelComponents:new Set([...E,...null!=n?n:[]])}}(t);var r;return{[`Element[name=${r=[...s],RegExp(`^(${r.join("|")})$`)}]`](t){const s=new Set([...t.attributes,...t.inputs].map(({name:e})=>e));if([...o].some(e=>s.has(e))||function(e,t){return Boolean([...e].some(e=>A(t,e)))}(a,t))return;const r=n.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:r,messageId:"accessibilityLabelFor"})}}}});const k="accessibility-label-has-associated-control",w=["input","meter","output","progress","select","textarea"],L=[{inputs:["for","htmlFor"],selector:"label"}];var N=l({name:k,meta:{type:"suggestion",docs:{description:"Ensures that a label element/component is associated with a form element",category:"Best Practices",recommended:!1},schema:[{additionalProperties:!1,properties:{controlComponents:{items:{type:"string"},type:"array",uniqueItems:!0},labelComponents:{items:{required:["selector"],type:"object"},type:"array",uniqueItems:!0}},type:"object"}],messages:{accessibilityLabelHasAssociatedControl:"A label component must be associated with a form element"}},defaultOptions:[{controlComponents:w,labelComponents:L}],create(e,[{controlComponents:t,labelComponents:n}]){const a=u(e),o=new Set([...w,...null!=t?t:[]]),s=[...L,...null!=n?n:[]],r=s.map(({selector:e})=>e);return{[`Element[name=${RegExp(`^(${r.join("|")})$`)}]`](t){var n;const r=s.find(({selector:e})=>e===t.name);if(!r)return;const i=new Set([...t.attributes,...t.inputs].map(({name:e})=>e));if((null==(n=r.inputs)?void 0:n.some(e=>i.has(e)))||function(e,t){return Boolean([...e].some(e=>A(t,e)))}(o,t))return;const c=a.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:c,messageId:"accessibilityLabelHasAssociatedControl"})}}}}),P=l({name:"accessibility-table-scope",meta:{type:"suggestion",docs:{description:"Ensures that scope is not used on any element except <th>",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityTableScope:"Scope attribute can only be on <th> element"}},defaultOptions:[],create(e){const t=u(e);return{'Element[name!="th"] > :matches(BoundAttribute[name="scope"], TextAttribute[name="scope"])'({sourceSpan:n}){const a=t.convertNodeSourceSpanToLoc(n);e.report({loc:a,messageId:"accessibilityTableScope"})}}}}),O=l({name:"accessibility-valid-aria",meta:{type:"suggestion",docs:{description:"Ensures that correct ARIA attributes and respective values are used",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityValidAria:"The `{{attribute}}` is an invalid ARIA attribute",accessibilityValidAriaValue:"The `{{attribute}}` has an invalid value. Check the valid values at https://raw.githack.com/w3c/aria/stable/#roles"}},defaultOptions:[],create(e){const t=u(e);return{"BoundAttribute[name=/^aria-.*/], TextAttribute[name=/^aria-.*/]"(o){const{name:s,sourceSpan:r}=o,i=a.aria.get(s),c=t.convertNodeSourceSpanToLoc(r);if(!i)return void e.report({loc:c,messageId:"accessibilityValidAria",data:{attribute:s}});const l=function(e){return e instanceof n.TmplAstBoundAttribute?e.value.ast:e}(o);(function(e){return!function(e){return e instanceof n.LiteralArray||e instanceof n.LiteralMap}(e)&&!function(e){return e instanceof n.LiteralPrimitive||e instanceof n.TmplAstTextAttribute}(e)})(l)||function({allowundefined:e,type:t,values:n},a){if(e&&$(a))return!0;switch(t){case"boolean":return F(a);case"tristate":return F(a)||$(a);case"id":case"idlist":return!0;case"integer":return o=a,!Number.isNaN(o)&&parseInt(Number(o))==o&&!Number.isNaN(parseInt(o,10));case"number":return function(e){return!Number.isNaN(Number.parseFloat(e))&&Number.isFinite(e)}(a);case"string":return function(e){return"string"==typeof e}(a);case"token":case"tokenlist":{const e=F(a)?JSON.parse(a):a;return Boolean(null==n?void 0:n.includes(e))}}var o}(i,l.value)||e.report({loc:c,messageId:"accessibilityValidAriaValue",data:{attribute:s}})}}}});function F(e){return"boolean"==typeof e||"false"===e||"true"===e}function $(e){return null==e}const j=/\[(.*)\]/;var q=l({name:"banana-in-box",meta:{type:"suggestion",docs:{description:"Ensures that the two-way data binding syntax is correct",category:"Best Practices",recommended:"error"},fixable:"code",schema:[],messages:{bananaInBox:"Invalid binding syntax. Use [(expr)] instead"}},defaultOptions:[],create(e){const t=u(e),n=e.getSourceCode();return{BoundEvent({name:a,sourceSpan:o}){const s=a.match(j);if(!s)return;const[,r]=s,i=`[(${r})]`,c=t.convertNodeSourceSpanToLoc(o),l=n.getIndexFromLoc(c.start);e.report({messageId:"bananaInBox",loc:c,fix:e=>e.replaceTextRange([l,l+a.length+2],i)})}}}});let D=null;function R(){return D||(D=new Set(a.dom.keys()))}const H=new Set(["presentation","none",p]);let M=null,W=null,U=null;var V=l({name:"click-events-have-key-events",meta:{type:"suggestion",docs:{description:"Ensures that the click event is accompanied with at least one key event keyup, keydown or keypress.",category:"Best Practices",recommended:!1},schema:[],messages:{clickEventsHaveKeyEvents:"click must be accompanied by either keyup, keydown or keypress event for accessibility."}},defaultOptions:[],create:e=>({Element(t){if(!R().has(t.name))return;if(function(e){const t=d(e,"role");return null!==t&&H.has(t)}(t)||x(t)||function(e){return R().has(e.name)&&function(e){function t(t){return e.name===t.name&&function(e=[],t){const n=[...t.attributes,...t.inputs];return e.every(e=>n.some(n=>"a"===t.name&&"routerLink"===n.name||e.name===n.name&&(!e.value||e.value===h(d(t,e.name)))))}(t.attributes,e)}return!!function(){if(null===M){const e=[...a.roles.keys()],t=[...a.elementRoles],n=new Set(e.filter(e=>{const t=a.roles.get(e);return!t.abstract&&"progressbar"!==e&&t.superClass.some(e=>e.includes("widget"))}).concat("toolbar"));M=t.reduce((e,[t,a])=>([...a].some(e=>n.has(e))&&e.push(t),e),[])}return M}().some(t)||!function(){if(null===W){const e=[...a.roles.keys()],t=[...a.elementRoles],n=new Set(e.filter(e=>{const t=a.roles.get(e);return!t.abstract&&"toolbar"!==e&&!t.superClass.some(e=>e.includes("widget"))}).concat("progressbar"));W=t.reduce((e,[t,a])=>([...a].every(e=>n.has(e))&&e.push(t),e),[])}return W}().some(t)&&!!function(){if(null===U){const{AXObjects:e,elementAXObjects:t}=require("axobject-query"),n=new Set(Array.from(e.keys()).filter(t=>"widget"===e.get(t).type));U=[...t].reduce((e,[t,a])=>([...a].every(e=>n.has(e))&&e.push(t),e),[])}return U}().some(t)}(e)}(t))return;let n=!1,o=!1;for(const e of t.outputs)n="click"===e.name,o=e.name.startsWith("keyup")||e.name.startsWith("keydown")||e.name.startsWith("keypress");if(!n||o)return;const s=u(e).convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:s,messageId:"clickEventsHaveKeyEvents"})}})}),_=l({name:"conditional-complexity",meta:{type:"suggestion",docs:{description:"The conditional complexity should not exceed a rational limit",category:"Best Practices",recommended:!1},schema:[{type:"object",properties:{maxComplexity:{minimum:1,type:"number"}},additionalProperties:!1}],messages:{"conditionalСomplexity":'The conditional complexity "{{totalComplexity}}" exceeds the defined limit "{{maxComplexity}}"'}},defaultOptions:[{maxComplexity:5}],create(e,[{maxComplexity:t}]){m(e);const a=e.getSourceCode();return{BoundAttribute(o){if(!o.value.source)return;const s=X(z((K||(K=new n.Parser(new n.Lexer))).parseBinding(o.value.source,"",0).ast));if(s<=t)return;const{sourceSpan:{start:r,end:i}}=o.value;e.report({loc:{start:a.getLocFromIndex(r),end:a.getLocFromIndex(i)},messageId:"conditionalСomplexity",data:{maxComplexity:t,totalComplexity:s}})},Interpolation({expressions:n}){for(const o of n){const n=X(o);if(n<=t)continue;const{sourceSpan:{start:s,end:r}}=o;e.report({loc:{start:a.getLocFromIndex(s),end:a.getLocFromIndex(r)},messageId:"conditionalСomplexity",data:{maxComplexity:t,totalComplexity:n}})}}}}});function z(e){return e instanceof n.BindingPipe?e.exp:e}let K=null;function X(e){const t=z(e);if(!(t instanceof n.Binary||t instanceof n.Conditional))return 0;let a=1;return t instanceof n.Binary&&(t.left instanceof n.Binary&&(a+=X(t.left)),t.right instanceof n.Binary&&(a+=X(t.right))),t instanceof n.Conditional&&(a+=X(t.condition)+X(t.trueExp)+X(t.falseExp)),a}var Y=l({name:"cyclomatic-complexity",meta:{type:"suggestion",docs:{description:"Checks cyclomatic complexity against a specified limit. It is a quantitative measure of the number of linearly independent paths through a program's source code",category:"Best Practices",recommended:!1},schema:[{type:"object",properties:{maxComplexity:{type:"number",minimum:1}},additionalProperties:!1}],messages:{cyclomaticComplexity:'The cyclomatic complexity "{{totalComplexity}}" exceeds the defined limit "{{maxComplexity}}"'}},defaultOptions:[{maxComplexity:5}],create(e,[{maxComplexity:t}]){let n=0;const a=u(e);return{'BoundAttribute[name=/^(ngForOf|ngIf|ngSwitchCase)$/], TextAttribute[name="ngSwitchDefault"]'({sourceSpan:o}){if(n+=1,n<=t)return;const s=a.convertNodeSourceSpanToLoc(o);e.report({messageId:"cyclomaticComplexity",loc:s,data:{maxComplexity:t,totalComplexity:n}})}}}});const J=/[a-z]/i,G=new Set(["charset","class","color","colspan","fill","formControlName","height","href","id","lang","src","stroke","stroke-width","style","svgIcon","tabindex","target","type","viewBox","width","xmlns"]),Q={checkAttributes:!0,checkId:!0,checkText:!0,ignoreAttributes:[...G]};var Z=l({name:"i18n",meta:{type:"suggestion",docs:{description:"Helps to ensure following best practices for i18n. Checks for missing i18n attributes on elements and non-ignored attributes containing text. Can also highlight tags that do not use custom ID (@@) feature. ",category:"Best Practices",recommended:!1,suggestion:!0},fixable:"code",schema:[{type:"object",properties:{boundTextAllowedPattern:{type:"string"},checkId:{type:"boolean",default:Q.checkId},checkText:{type:"boolean",default:Q.checkText},checkAttributes:{type:"boolean",default:Q.checkAttributes},ignoreAttributes:{type:"array",items:{type:"string"},default:[...G]},ignoreTags:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{i18nAttribute:"Attribute '{{attributeName}}' has no corresponding i18n attribute. See more at https://angular.io/guide/i18n#translate-attributes",i18nId:"Missing custom message identifier. See more at https://angular.io/guide/i18n#use-a-custom-id-with-a-description",i18nIdOnAttribute:'Missing custom message identifier on attribute "{{attributeName}}". See more at https://angular.io/guide/i18n#use-a-custom-id-with-a-description',i18nSuggestIgnore:'Add the attribute name "{{attributeName}}" to the `ignoreAttributes` option in the eslint config',i18nText:"Each element containing text node should have an i18n attribute. See more at https://angular.io/guide/i18n"}},defaultOptions:[Q],create(e,[{boundTextAllowedPattern:t,checkAttributes:a,checkId:o,checkText:s,ignoreAttributes:r,ignoreTags:i}]){const c=u(e),l=e.getSourceCode(),m=RegExp(null!=t?t:J),p=new Set([...G,...null!=r?r:[]]),d=new Set(i);function g(e){return e.customId}function f(e){return e instanceof n.TmplAstText&&/\S/.test(e.value)||e instanceof n.TmplAstBoundText&&e.value instanceof n.ASTWithSource&&e.value.ast instanceof n.Interpolation&&m.test(e.value.ast.strings.join("").trim())}function y(e,t,n){return p.has(t)||p.has(`${e}[${t}]`)||0===n.trim().length||"true"===n||"false"===n}function b({attributes:t,children:r,i18n:i,parent:u,sourceSpan:m},p){const d=c.convertNodeSourceSpanToLoc(m);for(const{i18n:n,name:s,value:r}of t)n?o&&!g(n)&&e.report({messageId:"i18nIdOnAttribute",loc:d,data:{attributeName:s}}):a&&y(p,s,r)||e.report({messageId:"i18nAttribute",loc:d,data:{attributeName:s},fix:e=>{const t=l.getIndexFromLoc(d.start)+1+p.length;return e.replaceTextRange([t,t],` i18n-${s}`)},suggest:[{messageId:"i18nSuggestIgnore",data:{attributeName:s},fix:e=>e.insertTextBeforeRange([0,0],"")}]});i?o&&!function(e,t){return!((e instanceof n.TmplAstElement||e instanceof n.TmplAstTemplate)&&(null==e||!e.i18n)&&!g(t))}(u,i)&&e.report({messageId:"i18nId",loc:d}):s&&r.some(f)&&e.report({messageId:"i18nText",loc:d})}return{Element(e){d.has(e.name)||b(e,e.name)},Template(e){b(e,e.tagName)}}}}),ee=l({name:"mouse-events-have-key-events",meta:{type:"suggestion",docs:{description:"Ensures that the Mouse Events mouseover and mouseout are accompanied with Key Events focus and blur.",category:"Best Practices",recommended:!1},schema:[],messages:{mouseOverEventHasFocusEvent:"mouseover must be accompanied by focus event for accessibility.",mouseOutEventHasBlurEvent:"mouseout must be accompanied by blur event for accessibility"}},defaultOptions:[],create(e){const t=u(e);return{Element(n){let a=!1,o=!1,s=!1,r=!1;for(const e of n.outputs)a="mouseover"===e.name,o="mouseout"===e.name,s="focus"===e.name,r="blur"===e.name;if(!a&&!o)return;const i=t.convertNodeSourceSpanToLoc(n.sourceSpan);a&&!s&&e.report({loc:i,messageId:"mouseOverEventHasFocusEvent"}),o&&!r&&e.report({loc:i,messageId:"mouseOutEventHasBlurEvent"})}}}}),te=l({name:"no-any",meta:{type:"suggestion",docs:{description:'The use of "$any" nullifies the compile-time benefits of the Angular\'s type system.',category:"Best Practices",recommended:!1},schema:[],messages:{noAny:'Avoid using "$any" in templates'}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{'MethodCall[name="$any"][receiver.expression=undefined][receiver.name=undefined]'({sourceSpan:{end:n,start:a}}){e.report({messageId:"noAny",loc:{start:t.getLocFromIndex(a),end:t.getLocFromIndex(n)}})}}}}),ne=l({name:"no-autofocus",meta:{type:"suggestion",docs:{description:"Ensure that autofocus attribute is not used",category:"Best Practices",recommended:!1},schema:[],messages:{noAutofocus:"autofocus attribute should not be used, as it reduces usability and accessibility for users"}},defaultOptions:[],create(e){const t=u(e);return{'TextAttribute[name="autofocus"], BoundAttribute[name="autofocus"]'(n){const a=t.convertNodeSourceSpanToLoc(n.sourceSpan);e.report({loc:a,messageId:"noAutofocus"})}}}});function ae({type:e}){return"Program"===e}var oe=l({name:"no-call-expression",meta:{type:"suggestion",docs:{description:"Disallows calling expressions in templates, except for output handlers",category:"Best Practices",recommended:!1},schema:[],messages:{noCallExpression:"Avoid calling expressions in templates"}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{'MethodCall[name!="$any"], SafeMethodCall'(n){if(function({parent:e},t){for(;e&&!ae(e);){if(t(e))return e;e=e.parent}return null}(n,se))return;const{sourceSpan:{start:a,end:o}}=n;e.report({messageId:"noCallExpression",loc:{start:t.getLocFromIndex(a),end:t.getLocFromIndex(o)}})}}}});function se(e){return e instanceof n.TmplAstBoundEvent}var re=l({name:"no-distracting-elements",meta:{type:"suggestion",docs:{description:"Enforces that no distracting elements are used",category:"Best Practices",recommended:!1},schema:[],messages:{noDistractingElements:"Do not use <{{element}}> elements as they can create visual accessibility issues and are deprecated"}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(blink|marquee)$/]"({name:n,sourceSpan:a}){const o=t.convertNodeSourceSpanToLoc(a);e.report({loc:o,messageId:"noDistractingElements",data:{element:n}})}}}}),ie=l({name:"no-duplicate-attributes",meta:{type:"problem",docs:{description:"Ensures that there are no duplicate input properties or output event listeners",category:"Possible Errors",recommended:!1},schema:[{type:"object",properties:{allowTwoWayDataBinding:{type:"boolean"}},additionalProperties:!1}],messages:{noDuplicateAttributes:'Duplicate attribute "{{attributeName}}"'}},defaultOptions:[{allowTwoWayDataBinding:!0}],create(e,[{allowTwoWayDataBinding:t}]){const n=u(e);return{Element({inputs:a,outputs:o,attributes:s}){[...le([...a,...s]),...le(t?o.filter(e=>!a.some(t=>t.sourceSpan.start===e.sourceSpan.start&&t.sourceSpan.end===e.sourceSpan.end)):o)].forEach(t=>{const a=n.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({messageId:"noDuplicateAttributes",loc:a,data:{attributeName:ce(t)}})})}}}});function ce(e){if("type"in e)if("BoundAttribute"===e.type)switch(e.__originalType){case 2:return`class.${e.name}`;case 3:return`style.${e.name}${e.unit?"."+e.unit:""}`;case 4:return`@${e.name}`}else if("BoundEvent"===e.type){if(1===e.__originalType)return`@${e.name}${e.phase?"."+e.phase:""}`;if(e.target)return`${e.target}:${e.name}`}return e.name}function le(e){return e.filter(t=>e.some(e=>e!==t&&ce(e)===ce(t)))}var ue=l({name:"no-negated-async",meta:{type:"suggestion",docs:{description:"Ensures that strict equality is used when evaluating negations on async pipe output",category:"Best Practices",recommended:"error"},schema:[],messages:{noNegatedAsync:"Async pipes should not be negated. Use (observable | async) === (false || null || undefined) to check its value instead",noLooseEquality:"Async pipes must use strict equality `===` when comparing with `false`"}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{"PrefixNot > BindingPipe[name=async]"({parent:n}){e.report({messageId:"noNegatedAsync",loc:{start:t.getLocFromIndex(n.sourceSpan.start),end:t.getLocFromIndex(n.sourceSpan.end)}})},'Binary[operation="=="] > BindingPipe[name=async]'({parent:n}){e.report({messageId:"noLooseEquality",loc:{start:t.getLocFromIndex(n.sourceSpan.start),end:t.getLocFromIndex(n.sourceSpan.end)}})}}}}),me=l({name:"no-positive-tabindex",meta:{type:"suggestion",docs:{description:"Ensures that the tabindex attribute is not positive",category:"Best Practices",recommended:!1},schema:[],messages:{noPositiveTabindex:"tabindex attribute cannot be positive"}},defaultOptions:[],create(e){const t=u(e);return{'BoundAttribute[name="tabindex"][value.ast.value>0], TextAttribute[name="tabindex"][value>0]'({sourceSpan:n}){const a=t.convertNodeSourceSpanToLoc(n);e.report({loc:a,messageId:"noPositiveTabindex"})}}}});function pe(){return(pe=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e}).apply(this,arguments)}var de=l({name:"use-track-by-function",meta:{type:"suggestion",docs:{description:"Ensures trackBy function is used.",category:"Best Practices",recommended:!1},schema:[],messages:{useTrackByFunction:"Missing trackBy function in ngFor directive"}},defaultOptions:[],create(e){const t=u(e);return{'BoundAttribute.inputs[name="ngForOf"]'(n){if(n.parent.inputs.some(e=>"BoundAttribute"===e.type&&"ngForTrackBy"===e.name))return;const a=t.convertNodeSourceSpanToLoc(n.sourceSpan);e.report({messageId:"useTrackByFunction",loc:a})},'BoundAttribute.templateAttrs[name="ngForOf"]'(n){const a=n.parent.templateAttrs;if(a.some(e=>"BoundAttribute"===e.type&&"ngForTrackBy"===e.name))return;const o=t.convertNodeSourceSpanToLoc(a[0].sourceSpan).start,s=t.convertNodeSourceSpanToLoc(a[a.length-1].sourceSpan).end,r={start:pe({},o,{column:o.column-1}),end:pe({},s,{column:s.column+1})};e.report({messageId:"useTrackByFunction",loc:r})}}}});module.exports={configs:{all:{extends:"./configs/base.json",rules:{"@angular-eslint/template/accessibility-alt-text":"error","@angular-eslint/template/accessibility-elements-content":"error","@angular-eslint/template/accessibility-label-for":"error","@angular-eslint/template/accessibility-label-has-associated-control":"error","@angular-eslint/template/accessibility-table-scope":"error","@angular-eslint/template/accessibility-valid-aria":"error","@angular-eslint/template/banana-in-box":"error","@angular-eslint/template/click-events-have-key-events":"error","@angular-eslint/template/conditional-complexity":"error","@angular-eslint/template/cyclomatic-complexity":"error","@angular-eslint/template/i18n":"error","@angular-eslint/template/mouse-events-have-key-events":"error","@angular-eslint/template/no-any":"error","@angular-eslint/template/no-autofocus":"error","@angular-eslint/template/no-call-expression":"error","@angular-eslint/template/no-distracting-elements":"error","@angular-eslint/template/no-duplicate-attributes":"error","@angular-eslint/template/no-negated-async":"error","@angular-eslint/template/no-positive-tabindex":"error","@angular-eslint/template/use-track-by-function":"error"}},base:{parser:"@angular-eslint/template-parser",plugins:["@angular-eslint/template"]},recommended:{extends:"./configs/base.json",rules:{"@angular-eslint/template/banana-in-box":"error","@angular-eslint/template/no-negated-async":"error"}},"process-inline-templates":{parser:"@typescript-eslint/parser",parserOptions:{ecmaVersion:2020,sourceType:"module"},plugins:["@angular-eslint/template"],processor:"@angular-eslint/template/extract-inline-html"}},processors:c,rules:{"accessibility-alt-text":g,"accessibility-elements-content":S,"accessibility-label-for":B,[k]:N,"accessibility-table-scope":P,"accessibility-valid-aria":O,"banana-in-box":q,"conditional-complexity":_,"click-events-have-key-events":V,"cyclomatic-complexity":Y,i18n:Z,"mouse-events-have-key-events":ee,"no-any":te,"no-autofocus":ne,"no-call-expression":oe,"no-distracting-elements":re,"no-duplicate-attributes":ie,"no-negated-async":ue,"no-positive-tabindex":me,"use-track-by-function":de}};
1
+ var e=require("typescript"),t=require("@typescript-eslint/experimental-utils"),n=require("@angular/compiler"),a=require("aria-query");function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=r(e);function s(e,t){const n=(e=e.replace(/\r\n/g,"\n")).indexOf(t);return[n,n+t.length]}const i=new Map;var c={"extract-inline-html":{preprocess:function(e,t){const n=[e];if(!function(e,t){return!![".component.ts",".page.ts",".dialog.ts",".modal.ts",".popover.ts",".bottomsheet.ts",".snackbar.ts"].some(e=>t.endsWith(e))||!(!e.includes("Component")||!e.includes("@angular/core"))}(e,t))return n;try{const a=o.default.createSourceFile(t,e,o.default.ScriptTarget.Latest,!0),r=a.statements.filter(e=>o.default.isClassDeclaration(e));if(!r||!r.length)return n;const c=[];for(const e of r)if(e.decorators)for(const t of e.decorators)o.default.isCallExpression(t.expression)&&o.default.isIdentifier(t.expression.expression)&&"Component"===t.expression.expression.text&&c.push(t);if(!c||!c.length)return n;const l=[e];let u=0;for(const t of c){if(!o.default.isDecorator(t)||!o.default.isCallExpression(t.expression)||1!==t.expression.arguments.length)continue;const n=t.expression.arguments[0];if(!o.default.isObjectLiteralExpression(n))continue;const r=n.properties.find(e=>e&&e.name&&"template"===e.name.getText());if(n.properties.find(e=>e&&e.name&&"templateUrl"===e.name.getText())||!r)continue;if(!o.default.isPropertyAssignment(r)||!o.default.isStringLiteralLike(r.initializer))continue;const c=r.initializer.text,m=s(e,c),p=`inline-template-${++u}.component.html`;i.set(p,{range:m,lineAndCharacter:{start:a.getLineAndCharacterOfPosition(m[0]),end:a.getLineAndCharacterOfPosition(m[1])}}),l.push({text:c,filename:p})}return l}catch(e){return console.log(e),console.error("preprocess: ERROR could not parse @Component() metadata",t),n}},postprocess:function(e,t){const n=e[0];if(1===e.length)return n;const a=e.slice(1);return[...n,...[].concat(...a.map((e,t)=>{const n=`inline-template-${++t}.component.html`,a=i.get(n);return a?e.map(e=>{if(e.line=e.line+a.lineAndCharacter.start.line,e.endLine=e.endLine+a.lineAndCharacter.start.line,e.fix){const t=a.range[0];e.fix.range=[t+e.fix.range[0],t+e.fix.range[1]]}return e}):[]}))]},supportsAutofix:!0}};const l=t.ESLintUtils.RuleCreator(e=>"https://github.com/angular-eslint/angular-eslint");function u(e){return m(e),e.parserServices}function m(e){var t,n;if(null==(t=e.parserServices)||!t.convertNodeSourceSpanToLoc||null==(n=e.parserServices)||!n.convertElementSourceSpanToLoc)throw new Error("You have used a rule which requires '@angular-eslint/template-parser' to be used as the 'parser' in your ESLint config.")}const p=Symbol("PROPERTY");function d(e,t){const a=e.attributes.find(e=>e.name===t);if(a)return a.value;const r=e.inputs.find(e=>e.name===t);return r&&r.value instanceof n.ASTWithSource?r.value.ast instanceof n.LiteralPrimitive?r.value.ast.value:p:null}var g=l({name:"accessibility-alt-text",meta:{type:"suggestion",docs:{description:"Enforces alternate text for elements which require the alt, aria-label, aria-labelledby attributes.",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityAltText:"<{{element}}/> element must have a text alternative."}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(img|area|object|input)$/]"(n){if(!function(e){return"img"===e.name?function(e){return e.attributes.some(({name:e})=>b(e))||e.inputs.some(({name:e})=>b(e))}(e):"object"===e.name?function(e){let t=!1,n=!1;for(const a of e.attributes)t="title"===a.name,n=y(a.name);if(t||n)return!0;let a=!1,r=!1;for(const t of e.inputs)a="title"===t.name,r=y(t.name);return!(!a&&!r)||e.children.length>0&&!!e.children[0].value}(e):"area"===e.name?f(e):function(e){return"image"!==d(e,"type")||f(e)}(e)}(n)){const a=t.convertElementSourceSpanToLoc(e,n);e.report({loc:a,messageId:"accessibilityAltText",data:{element:n.name}})}}}}});function f(e){let t=!1,n=!1;for(const a of e.attributes)t=b(a.name),n=y(a.name);if(t||n)return!0;let a=!1,r=!1;for(const t of e.inputs)a=b(t.name),r=y(t.name);return a||r}function y(e){return"aria-label"===e||"aria-labelledby"===e}function b(e){return"alt"===e}function h(e){if("string"==typeof e){if("true"===e)return!0;if("false"===e)return!1}return e}function x(e){if("INPUT"===e.name.toUpperCase()){const t=d(e,"type");if("string"==typeof t&&"HIDDEN"===t.toUpperCase())return!0}const t=h(d(e,"aria-hidden"));return""===t||t===p||!0===t}const v=new Set(["aria-label","innerHtml","innerHTML","innerText","outerHTML","title"]);var S=l({name:"accessibility-elements-content",meta:{type:"suggestion",docs:{description:"Ensures that the heading, anchor and button elements have content in it",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityElementsContent:"<{{element}}> should have content"}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(a|button|h1|h2|h3|h4|h5|h6)$/][children.length=0]"(n){if(x(n))return;const{attributes:a,inputs:r,name:o,sourceSpan:s}=n;if([...a,...r].map(({name:e})=>e).some(e=>v.has(e)))return;const i=t.convertNodeSourceSpanToLoc(s);e.report({loc:i,messageId:"accessibilityElementsContent",data:{element:o}})}}}});function A(e,t){return function e({children:a}){return a.some(a=>a instanceof n.TmplAstElement&&(a.name===t||e(a)))}(e)}const T={items:{type:"string"},type:"array",uniqueItems:!0},C=["button","input","meter","output","progress","select","textarea"],I=["for","htmlFor"],E=["label"];var B=l({name:"accessibility-label-for",meta:{deprecated:!0,replacedBy:["accessibility-label-has-associated-control"],type:"suggestion",docs:{description:"Ensures that a label element/component is associated with a form element",category:"Best Practices",recommended:!1},schema:[{additionalProperties:!1,properties:{controlComponents:T,labelAttributes:T,labelComponents:T},type:"object"}],messages:{accessibilityLabelFor:"A label element/component must be associated with a form element"}},defaultOptions:[{controlComponents:C,labelAttributes:I,labelComponents:E}],create(e,[t]){const n=u(e),{controlComponents:a,labelAttributes:r,labelComponents:o}=function({controlComponents:e,labelAttributes:t,labelComponents:n}){return{controlComponents:new Set([...C,...null!=e?e:[]]),labelAttributes:new Set([...I,...null!=t?t:[]]),labelComponents:new Set([...E,...null!=n?n:[]])}}(t);var s;return{[`Element[name=${s=[...o],RegExp(`^(${s.join("|")})$`)}]`](t){const o=new Set([...t.attributes,...t.inputs].map(({name:e})=>e));if([...r].some(e=>o.has(e))||function(e,t){return Boolean([...e].some(e=>A(t,e)))}(a,t))return;const s=n.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:s,messageId:"accessibilityLabelFor"})}}}});const k="accessibility-label-has-associated-control",w=["input","meter","output","progress","select","textarea"],L=[{inputs:["for","htmlFor"],selector:"label"}];var O=l({name:k,meta:{type:"suggestion",docs:{description:"Ensures that a label element/component is associated with a form element",category:"Best Practices",recommended:!1},schema:[{additionalProperties:!1,properties:{controlComponents:{items:{type:"string"},type:"array",uniqueItems:!0},labelComponents:{items:{additionalProperties:!1,properties:{inputs:{items:{type:"string"},type:"array",uniqueItems:!0},selector:{type:"string"}},required:["selector"],type:"object"},type:"array",uniqueItems:!0}},type:"object"}],messages:{accessibilityLabelHasAssociatedControl:"A label component must be associated with a form element"}},defaultOptions:[{controlComponents:w,labelComponents:L}],create(e,[{controlComponents:t,labelComponents:n}]){const a=u(e),r=new Set([...w,...null!=t?t:[]]),o=[...L,...null!=n?n:[]],s=o.map(({selector:e})=>e);return{[`Element[name=${RegExp(`^(${s.join("|")})$`)}]`](t){var n;const s=o.find(({selector:e})=>e===t.name);if(!s)return;const i=new Set([...t.attributes,...t.inputs].map(({name:e})=>e));if((null==(n=s.inputs)?void 0:n.some(e=>i.has(e)))||function(e,t){return Boolean([...e].some(e=>A(t,e)))}(r,t))return;const c=a.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:c,messageId:"accessibilityLabelHasAssociatedControl"})}}}}),N=l({name:"accessibility-table-scope",meta:{type:"suggestion",docs:{description:"Ensures that scope is not used on any element except <th>",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityTableScope:"Scope attribute can only be on <th> element"}},defaultOptions:[],create(e){const t=u(e);return{'Element[name!="th"] > :matches(BoundAttribute[name="scope"], TextAttribute[name="scope"])'({sourceSpan:n}){const a=t.convertNodeSourceSpanToLoc(n);e.report({loc:a,messageId:"accessibilityTableScope"})}}}}),P=l({name:"accessibility-valid-aria",meta:{type:"suggestion",docs:{description:"Ensures that correct ARIA attributes and respective values are used",category:"Best Practices",recommended:!1},schema:[],messages:{accessibilityValidAria:"The `{{attribute}}` is an invalid ARIA attribute",accessibilityValidAriaValue:"The `{{attribute}}` has an invalid value. Check the valid values at https://raw.githack.com/w3c/aria/stable/#roles"}},defaultOptions:[],create(e){const t=u(e);return{"BoundAttribute[name=/^aria-.*/], TextAttribute[name=/^aria-.*/]"(r){const{name:o,sourceSpan:s}=r,i=a.aria.get(o),c=t.convertNodeSourceSpanToLoc(s);if(!i)return void e.report({loc:c,messageId:"accessibilityValidAria",data:{attribute:o}});const l=function(e){return e instanceof n.TmplAstBoundAttribute?e.value.ast:e}(r);(function(e){return!function(e){return e instanceof n.LiteralArray||e instanceof n.LiteralMap}(e)&&!function(e){return e instanceof n.LiteralPrimitive||e instanceof n.TmplAstTextAttribute}(e)})(l)||function({allowundefined:e,type:t,values:n},a){if(e&&F(a))return!0;switch(t){case"boolean":return q(a);case"tristate":return q(a)||F(a);case"id":case"idlist":return!0;case"integer":return r=a,!Number.isNaN(r)&&parseInt(Number(r))==r&&!Number.isNaN(parseInt(r,10));case"number":return function(e){return!Number.isNaN(Number.parseFloat(e))&&Number.isFinite(e)}(a);case"string":return function(e){return"string"==typeof e}(a);case"token":case"tokenlist":{const e=q(a)?JSON.parse(a):a;return Boolean(null==n?void 0:n.includes(e))}}var r}(i,l.value)||e.report({loc:c,messageId:"accessibilityValidAriaValue",data:{attribute:o}})}}}});function q(e){return"boolean"==typeof e||"false"===e||"true"===e}function F(e){return null==e}const $=/\[(.*)\]/;var j=l({name:"banana-in-box",meta:{type:"suggestion",docs:{description:"Ensures that the two-way data binding syntax is correct",category:"Best Practices",recommended:"error"},fixable:"code",schema:[],messages:{bananaInBox:"Invalid binding syntax. Use [(expr)] instead"}},defaultOptions:[],create(e){const t=u(e),n=e.getSourceCode();return{BoundEvent({name:a,sourceSpan:r}){const o=a.match($);if(!o)return;const[,s]=o,i=`[(${s})]`,c=t.convertNodeSourceSpanToLoc(r),l=n.getIndexFromLoc(c.start);e.report({messageId:"bananaInBox",loc:c,fix:e=>e.replaceTextRange([l,l+a.length+2],i)})}}}});let R=null;function D(){return R||(R=new Set(a.dom.keys()))}const H=new Set(["presentation","none",p]);let M=null,U=null,W=null;var V=l({name:"click-events-have-key-events",meta:{type:"suggestion",docs:{description:"Ensures that the click event is accompanied with at least one key event keyup, keydown or keypress.",category:"Best Practices",recommended:!1},schema:[],messages:{clickEventsHaveKeyEvents:"click must be accompanied by either keyup, keydown or keypress event for accessibility."}},defaultOptions:[],create:e=>({Element(t){if(!D().has(t.name))return;if(function(e){const t=d(e,"role");return null!==t&&H.has(t)}(t)||x(t)||function(e){return D().has(e.name)&&function(e){function t(t){return e.name===t.name&&function(e=[],t){const n=[...t.attributes,...t.inputs];return e.every(e=>n.some(n=>"a"===t.name&&"routerLink"===n.name||e.name===n.name&&(!e.value||e.value===h(d(t,e.name)))))}(t.attributes,e)}return!!function(){if(null===M){const e=[...a.roles.keys()],t=[...a.elementRoles],n=new Set(e.filter(e=>{const t=a.roles.get(e);return!t.abstract&&"progressbar"!==e&&t.superClass.some(e=>e.includes("widget"))}).concat("toolbar"));M=t.reduce((e,[t,a])=>([...a].some(e=>n.has(e))&&e.push(t),e),[])}return M}().some(t)||!function(){if(null===U){const e=[...a.roles.keys()],t=[...a.elementRoles],n=new Set(e.filter(e=>{const t=a.roles.get(e);return!t.abstract&&"toolbar"!==e&&!t.superClass.some(e=>e.includes("widget"))}).concat("progressbar"));U=t.reduce((e,[t,a])=>([...a].every(e=>n.has(e))&&e.push(t),e),[])}return U}().some(t)&&!!function(){if(null===W){const{AXObjects:e,elementAXObjects:t}=require("axobject-query"),n=new Set(Array.from(e.keys()).filter(t=>"widget"===e.get(t).type));W=[...t].reduce((e,[t,a])=>([...a].every(e=>n.has(e))&&e.push(t),e),[])}return W}().some(t)}(e)}(t))return;let n=!1,r=!1;for(const e of t.outputs)n="click"===e.name,r=e.name.startsWith("keyup")||e.name.startsWith("keydown")||e.name.startsWith("keypress");if(!n||r)return;const o=u(e).convertNodeSourceSpanToLoc(t.sourceSpan);e.report({loc:o,messageId:"clickEventsHaveKeyEvents"})}})}),_=l({name:"conditional-complexity",meta:{type:"suggestion",docs:{description:"The conditional complexity should not exceed a rational limit",category:"Best Practices",recommended:!1},schema:[{type:"object",properties:{maxComplexity:{minimum:1,type:"number"}},additionalProperties:!1}],messages:{"conditionalСomplexity":'The conditional complexity "{{totalComplexity}}" exceeds the defined limit "{{maxComplexity}}"'}},defaultOptions:[{maxComplexity:5}],create(e,[{maxComplexity:t}]){m(e);const a=e.getSourceCode();return{BoundAttribute(r){if(!r.value.source)return;const o=X(z((K||(K=new n.Parser(new n.Lexer))).parseBinding(r.value.source,"",0).ast));if(o<=t)return;const{sourceSpan:{start:s,end:i}}=r.value;e.report({loc:{start:a.getLocFromIndex(s),end:a.getLocFromIndex(i)},messageId:"conditionalСomplexity",data:{maxComplexity:t,totalComplexity:o}})},Interpolation({expressions:n}){for(const r of n){const n=X(r);if(n<=t)continue;const{sourceSpan:{start:o,end:s}}=r;e.report({loc:{start:a.getLocFromIndex(o),end:a.getLocFromIndex(s)},messageId:"conditionalСomplexity",data:{maxComplexity:t,totalComplexity:n}})}}}}});function z(e){return e instanceof n.BindingPipe?e.exp:e}let K=null;function X(e){const t=z(e);if(!(t instanceof n.Binary||t instanceof n.Conditional))return 0;let a=1;return t instanceof n.Binary&&(t.left instanceof n.Binary&&(a+=X(t.left)),t.right instanceof n.Binary&&(a+=X(t.right))),t instanceof n.Conditional&&(a+=X(t.condition)+X(t.trueExp)+X(t.falseExp)),a}var Y=l({name:"cyclomatic-complexity",meta:{type:"suggestion",docs:{description:"Checks cyclomatic complexity against a specified limit. It is a quantitative measure of the number of linearly independent paths through a program's source code",category:"Best Practices",recommended:!1},schema:[{type:"object",properties:{maxComplexity:{type:"number",minimum:1}},additionalProperties:!1}],messages:{cyclomaticComplexity:'The cyclomatic complexity "{{totalComplexity}}" exceeds the defined limit "{{maxComplexity}}"'}},defaultOptions:[{maxComplexity:5}],create(e,[{maxComplexity:t}]){let n=0;const a=u(e);return{'BoundAttribute[name=/^(ngForOf|ngIf|ngSwitchCase)$/], TextAttribute[name="ngSwitchDefault"]'({sourceSpan:r}){if(n+=1,n<=t)return;const o=a.convertNodeSourceSpanToLoc(r);e.report({messageId:"cyclomaticComplexity",loc:o,data:{maxComplexity:t,totalComplexity:n}})}}}});function J({type:e}){return"Program"===e}function G({parent:e},t){for(;e&&!J(e);){if(t(e))return e;e=e.parent}return null}const Q={allowNullOrUndefined:!1};var Z=l({name:"eqeqeq",meta:{type:"suggestion",docs:{description:"Requires `===` and `!==` in place of `==` and `!=`",category:"Best Practices",recommended:"error"},fixable:"code",schema:[{type:"object",properties:{allowNullOrUndefined:{type:"boolean",default:Q.allowNullOrUndefined}},additionalProperties:!1}],messages:{eqeqeq:"Expected `{{expectedOperation}}` but received `{{actualOperation}}`"}},defaultOptions:[Q],create(e,[{allowNullOrUndefined:t}]){m(e);const n=e.getSourceCode();return{"Binary[operation=/^(==|!=)$/]"(a){const{left:r,operation:o,right:s,sourceSpan:{start:i,end:c}}=a,l=[r,s].some(ne);t&&l||e.report({loc:{start:n.getLocFromIndex(i),end:n.getLocFromIndex(c)},messageId:"eqeqeq",data:{actualOperation:o,expectedOperation:`${o}=`},fix:e=>{var t;const{source:n}=null!=(t=G(a,te))?t:{};return n?e.insertTextAfterRange([i+ee(r)+1,c-ee(s)-1],"="):[]}})}}}});function ee({span:{start:e,end:t}}){return t-e}function te(e){return e instanceof n.ASTWithSource}function ne(e){return e instanceof n.LiteralPrimitive&&null==e.value}const ae=/[a-z]/i,re=new Set(["charset","class","color","colspan","fill","formControlName","height","href","id","lang","src","stroke","stroke-width","style","svgIcon","tabindex","target","type","viewBox","width","xmlns"]),oe={checkAttributes:!0,checkId:!0,checkText:!0,ignoreAttributes:[...re]};var se=l({name:"i18n",meta:{type:"suggestion",docs:{description:"Helps to ensure following best practices for i18n. Checks for missing i18n attributes on elements and non-ignored attributes containing text. Can also highlight tags that do not use custom ID (@@) feature. ",category:"Best Practices",recommended:!1,suggestion:!0},fixable:"code",schema:[{type:"object",properties:{boundTextAllowedPattern:{type:"string"},checkId:{type:"boolean",default:oe.checkId},checkText:{type:"boolean",default:oe.checkText},checkAttributes:{type:"boolean",default:oe.checkAttributes},ignoreAttributes:{type:"array",items:{type:"string"},default:[...re]},ignoreTags:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{i18nAttribute:"Attribute '{{attributeName}}' has no corresponding i18n attribute. See more at https://angular.io/guide/i18n#translate-attributes",i18nId:"Missing custom message identifier. See more at https://angular.io/guide/i18n#use-a-custom-id-with-a-description",i18nIdOnAttribute:'Missing custom message identifier on attribute "{{attributeName}}". See more at https://angular.io/guide/i18n#use-a-custom-id-with-a-description',i18nSuggestIgnore:'Add the attribute name "{{attributeName}}" to the `ignoreAttributes` option in the eslint config',i18nText:"Each element containing text node should have an i18n attribute. See more at https://angular.io/guide/i18n"}},defaultOptions:[oe],create(e,[{boundTextAllowedPattern:t,checkAttributes:a,checkId:r,checkText:o,ignoreAttributes:s,ignoreTags:i}]){const c=u(e),l=e.getSourceCode(),m=RegExp(null!=t?t:ae),p=new Set([...re,...null!=s?s:[]]),d=new Set(i);function g(e){return e.customId}function f(e){return e instanceof n.TmplAstText&&/\S/.test(e.value)||e instanceof n.TmplAstBoundText&&e.value instanceof n.ASTWithSource&&e.value.ast instanceof n.Interpolation&&m.test(e.value.ast.strings.join("").trim())}function y(e,t,n){return p.has(t)||p.has(`${e}[${t}]`)||0===n.trim().length||"true"===n||"false"===n}function b({attributes:t,children:s,i18n:i,parent:u,sourceSpan:m},p){const d=c.convertNodeSourceSpanToLoc(m);for(const{i18n:n,name:o,value:s}of t)n?r&&!g(n)&&e.report({messageId:"i18nIdOnAttribute",loc:d,data:{attributeName:o}}):a&&y(p,o,s)||e.report({messageId:"i18nAttribute",loc:d,data:{attributeName:o},fix:e=>{const t=l.getIndexFromLoc(d.start)+1+p.length;return e.replaceTextRange([t,t],` i18n-${o}`)},suggest:[{messageId:"i18nSuggestIgnore",data:{attributeName:o},fix:e=>e.insertTextBeforeRange([0,0],"")}]});i?r&&!function(e,t){return!((e instanceof n.TmplAstElement||e instanceof n.TmplAstTemplate)&&(null==e||!e.i18n)&&!g(t))}(u,i)&&e.report({messageId:"i18nId",loc:d}):o&&s.some(f)&&e.report({messageId:"i18nText",loc:d})}return{Element(e){d.has(e.name)||b(e,e.name)},Template(e){b(e,e.tagName)}}}}),ie=l({name:"mouse-events-have-key-events",meta:{type:"suggestion",docs:{description:"Ensures that the Mouse Events mouseover and mouseout are accompanied with Key Events focus and blur.",category:"Best Practices",recommended:!1},schema:[],messages:{mouseOverEventHasFocusEvent:"mouseover must be accompanied by focus event for accessibility.",mouseOutEventHasBlurEvent:"mouseout must be accompanied by blur event for accessibility"}},defaultOptions:[],create(e){const t=u(e);return{Element(n){let a=!1,r=!1,o=!1,s=!1;for(const e of n.outputs)a="mouseover"===e.name,r="mouseout"===e.name,o="focus"===e.name,s="blur"===e.name;if(!a&&!r)return;const i=t.convertNodeSourceSpanToLoc(n.sourceSpan);a&&!o&&e.report({loc:i,messageId:"mouseOverEventHasFocusEvent"}),r&&!s&&e.report({loc:i,messageId:"mouseOutEventHasBlurEvent"})}}}}),ce=l({name:"no-any",meta:{type:"suggestion",docs:{description:'The use of "$any" nullifies the compile-time benefits of the Angular\'s type system.',category:"Best Practices",recommended:!1},schema:[],messages:{noAny:'Avoid using "$any" in templates'}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{'MethodCall[name="$any"][receiver.expression=undefined][receiver.name=undefined]'({sourceSpan:{end:n,start:a}}){e.report({messageId:"noAny",loc:{start:t.getLocFromIndex(a),end:t.getLocFromIndex(n)}})}}}}),le=l({name:"no-autofocus",meta:{type:"suggestion",docs:{description:"Ensure that autofocus attribute is not used",category:"Best Practices",recommended:!1},schema:[],messages:{noAutofocus:"autofocus attribute should not be used, as it reduces usability and accessibility for users"}},defaultOptions:[],create(e){const t=u(e);return{'TextAttribute[name="autofocus"], BoundAttribute[name="autofocus"]'(n){const a=t.convertNodeSourceSpanToLoc(n.sourceSpan);e.report({loc:a,messageId:"noAutofocus"})}}}}),ue=l({name:"no-call-expression",meta:{type:"suggestion",docs:{description:"Disallows calling expressions in templates, except for output handlers",category:"Best Practices",recommended:!1},schema:[],messages:{noCallExpression:"Avoid calling expressions in templates"}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{'MethodCall[name!="$any"], SafeMethodCall'(n){if(G(n,me))return;const{sourceSpan:{start:a,end:r}}=n;e.report({messageId:"noCallExpression",loc:{start:t.getLocFromIndex(a),end:t.getLocFromIndex(r)}})}}}});function me(e){return e instanceof n.TmplAstBoundEvent}var pe=l({name:"no-distracting-elements",meta:{type:"suggestion",docs:{description:"Enforces that no distracting elements are used",category:"Best Practices",recommended:!1},schema:[],messages:{noDistractingElements:"Do not use <{{element}}> elements as they can create visual accessibility issues and are deprecated"}},defaultOptions:[],create(e){const t=u(e);return{"Element[name=/^(blink|marquee)$/]"({name:n,sourceSpan:a}){const r=t.convertNodeSourceSpanToLoc(a);e.report({loc:r,messageId:"noDistractingElements",data:{element:n}})}}}}),de=l({name:"no-duplicate-attributes",meta:{type:"problem",docs:{description:"Ensures that there are no duplicate input properties or output event listeners",category:"Possible Errors",recommended:!1},schema:[{type:"object",properties:{allowTwoWayDataBinding:{type:"boolean"}},additionalProperties:!1}],messages:{noDuplicateAttributes:'Duplicate attribute "{{attributeName}}"'}},defaultOptions:[{allowTwoWayDataBinding:!0}],create(e,[{allowTwoWayDataBinding:t}]){const n=u(e);return{Element({inputs:a,outputs:r,attributes:o}){[...fe([...a,...o]),...fe(t?r.filter(e=>!a.some(t=>t.sourceSpan.start===e.sourceSpan.start&&t.sourceSpan.end===e.sourceSpan.end)):r)].forEach(t=>{const a=n.convertNodeSourceSpanToLoc(t.sourceSpan);e.report({messageId:"noDuplicateAttributes",loc:a,data:{attributeName:ge(t)}})})}}}});function ge(e){if("type"in e)if("BoundAttribute"===e.type)switch(e.__originalType){case 2:return`class.${e.name}`;case 3:return`style.${e.name}${e.unit?"."+e.unit:""}`;case 4:return`@${e.name}`}else if("BoundEvent"===e.type){if(1===e.__originalType)return`@${e.name}${e.phase?"."+e.phase:""}`;if(e.target)return`${e.target}:${e.name}`}return e.name}function fe(e){return e.filter(t=>e.some(e=>e!==t&&ge(e)===ge(t)))}var ye=l({name:"no-negated-async",meta:{type:"suggestion",docs:{description:"Ensures that async pipe results are not negated",category:"Best Practices",recommended:"error"},schema:[],messages:{noNegatedAsync:"Async pipe results should not be negated. Use (observable | async) === (false || null || undefined) to check its value instead"}},defaultOptions:[],create(e){m(e);const t=e.getSourceCode();return{'PrefixNot > BindingPipe[name="async"]'({parent:{sourceSpan:n}}){e.report({messageId:"noNegatedAsync",loc:{start:t.getLocFromIndex(n.start),end:t.getLocFromIndex(n.end)}})}}}}),be=l({name:"no-positive-tabindex",meta:{type:"suggestion",docs:{description:"Ensures that the tabindex attribute is not positive",category:"Best Practices",recommended:!1},schema:[],messages:{noPositiveTabindex:"tabindex attribute cannot be positive"}},defaultOptions:[],create(e){const t=u(e);return{'BoundAttribute[name="tabindex"][value.ast.value>0], TextAttribute[name="tabindex"][value>0]'({sourceSpan:n}){const a=t.convertNodeSourceSpanToLoc(n);e.report({loc:a,messageId:"noPositiveTabindex"})}}}});function he(){return(he=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e}).apply(this,arguments)}var xe=l({name:"use-track-by-function",meta:{type:"suggestion",docs:{description:"Ensures trackBy function is used.",category:"Best Practices",recommended:!1},schema:[],messages:{useTrackByFunction:"Missing trackBy function in ngFor directive"}},defaultOptions:[],create(e){const t=u(e);return{'BoundAttribute.inputs[name="ngForOf"]'(n){if(n.parent.inputs.some(e=>"BoundAttribute"===e.type&&"ngForTrackBy"===e.name))return;const a=t.convertNodeSourceSpanToLoc(n.sourceSpan);e.report({messageId:"useTrackByFunction",loc:a})},'BoundAttribute.templateAttrs[name="ngForOf"]'(n){const a=n.parent.templateAttrs;if(a.some(e=>"BoundAttribute"===e.type&&"ngForTrackBy"===e.name))return;const r=t.convertNodeSourceSpanToLoc(a[0].sourceSpan).start,o=t.convertNodeSourceSpanToLoc(a[a.length-1].sourceSpan).end,s={start:he({},r,{column:r.column-1}),end:he({},o,{column:o.column+1})};e.report({messageId:"useTrackByFunction",loc:s})}}}});module.exports={configs:{all:{extends:"./configs/base.json",rules:{"@angular-eslint/template/accessibility-alt-text":"error","@angular-eslint/template/accessibility-elements-content":"error","@angular-eslint/template/accessibility-label-for":"error","@angular-eslint/template/accessibility-label-has-associated-control":"error","@angular-eslint/template/accessibility-table-scope":"error","@angular-eslint/template/accessibility-valid-aria":"error","@angular-eslint/template/banana-in-box":"error","@angular-eslint/template/click-events-have-key-events":"error","@angular-eslint/template/conditional-complexity":"error","@angular-eslint/template/cyclomatic-complexity":"error","@angular-eslint/template/eqeqeq":"error","@angular-eslint/template/i18n":"error","@angular-eslint/template/mouse-events-have-key-events":"error","@angular-eslint/template/no-any":"error","@angular-eslint/template/no-autofocus":"error","@angular-eslint/template/no-call-expression":"error","@angular-eslint/template/no-distracting-elements":"error","@angular-eslint/template/no-duplicate-attributes":"error","@angular-eslint/template/no-negated-async":"error","@angular-eslint/template/no-positive-tabindex":"error","@angular-eslint/template/use-track-by-function":"error"}},base:{parser:"@angular-eslint/template-parser",plugins:["@angular-eslint/template"]},recommended:{extends:"./configs/base.json",rules:{"@angular-eslint/template/banana-in-box":"error","@angular-eslint/template/eqeqeq":"error","@angular-eslint/template/no-negated-async":"error"}},"process-inline-templates":{parser:"@typescript-eslint/parser",parserOptions:{ecmaVersion:2020,sourceType:"module"},plugins:["@angular-eslint/template"],processor:"@angular-eslint/template/extract-inline-html"}},processors:c,rules:{"accessibility-alt-text":g,"accessibility-elements-content":S,"accessibility-label-for":B,[k]:O,"accessibility-table-scope":N,"accessibility-valid-aria":P,"banana-in-box":j,"conditional-complexity":_,"click-events-have-key-events":V,"cyclomatic-complexity":Y,eqeqeq:Z,i18n:se,"mouse-events-have-key-events":ie,"no-any":ce,"no-autofocus":le,"no-call-expression":ue,"no-distracting-elements":pe,"no-duplicate-attributes":de,"no-negated-async":ye,"no-positive-tabindex":be,"use-track-by-function":xe}};
@@ -0,0 +1,7 @@
1
+ declare type Options = [{
2
+ readonly allowNullOrUndefined?: boolean;
3
+ }];
4
+ export declare type MessageIds = 'eqeqeq';
5
+ export declare const RULE_NAME = "eqeqeq";
6
+ declare const _default: import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"eqeqeq", Options, import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export default _default;
@@ -1,4 +1,4 @@
1
- export declare type MessageIds = 'noNegatedAsync' | 'noLooseEquality';
1
+ export declare type MessageIds = 'noNegatedAsync';
2
2
  export declare const RULE_NAME = "no-negated-async";
3
- declare const _default: import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
3
+ declare const _default: import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleModule<"noNegatedAsync", [], import("@typescript-eslint/experimental-utils/dist/ts-eslint/Rule").RuleListener>;
4
4
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "12.0.0-alpha.2",
3
+ "version": "12.0.0",
4
4
  "description": "ESLint plugin for Angular Templates",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -24,12 +24,12 @@
24
24
  "LICENSE"
25
25
  ],
26
26
  "dependencies": {
27
- "@typescript-eslint/experimental-utils": "4.16.1",
27
+ "@typescript-eslint/experimental-utils": "4.23.0",
28
28
  "aria-query": "^4.2.2",
29
29
  "axobject-query": "^2.2.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@angular-eslint/utils": "12.0.0-alpha.2",
32
+ "@angular-eslint/utils": "12.0.0",
33
33
  "@types/aria-query": "^4.2.0"
34
34
  },
35
35
  "peerDependencies": {
@@ -37,5 +37,5 @@
37
37
  "eslint": "*",
38
38
  "typescript": "*"
39
39
  },
40
- "gitHead": "5f45f17163e69f9c19c24ea91d09782d5c3f3344"
40
+ "gitHead": "303d5550dabf63b2728ac2f552bc6666a30d5abc"
41
41
  }