@angular-eslint/eslint-plugin 15.2.2-alpha.9 → 16.0.0-alpha.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/dist/configs/README.md +9 -24
- package/dist/configs/all.json +2 -1
- package/dist/configs/recommended.json +2 -1
- package/dist/index.js +101 -1
- package/dist/rules/component-class-suffix.js +55 -0
- package/dist/rules/component-max-inline-declarations.js +102 -0
- package/dist/rules/component-selector.js +124 -0
- package/dist/rules/contextual-decorator.js +60 -0
- package/dist/rules/contextual-lifecycle.js +58 -0
- package/dist/rules/directive-class-suffix.js +60 -0
- package/dist/rules/directive-selector.js +89 -0
- package/dist/rules/no-attribute-decorator.js +30 -0
- package/dist/rules/no-conflicting-lifecycle.js +71 -0
- package/dist/rules/no-empty-lifecycle-method.js +66 -0
- package/dist/rules/no-forward-ref.js +31 -0
- package/dist/rules/no-host-metadata-property.js +68 -0
- package/dist/rules/no-input-prefix.js +105 -0
- package/dist/rules/no-input-rename.js +175 -0
- package/dist/rules/no-inputs-metadata-property.js +52 -0
- package/dist/rules/no-lifecycle-call.js +47 -0
- package/dist/rules/no-output-native.js +44 -0
- package/dist/rules/no-output-on-prefix.js +45 -0
- package/dist/rules/no-output-rename.js +107 -0
- package/dist/rules/no-outputs-metadata-property.js +33 -0
- package/dist/rules/no-pipe-impure.js +40 -0
- package/dist/rules/no-queries-metadata-property.js +37 -0
- package/dist/rules/pipe-prefix.js +79 -0
- package/dist/rules/prefer-on-push-component-change-detection.js +79 -0
- package/dist/rules/prefer-output-readonly.js +39 -0
- package/dist/rules/relative-url-prefix.js +55 -0
- package/dist/rules/require-localize-metadata.js +74 -0
- package/dist/rules/sort-ngmodule-metadata-arrays.js +63 -0
- package/dist/rules/use-component-selector.js +37 -0
- package/dist/rules/use-component-view-encapsulation.js +48 -0
- package/dist/rules/use-injectable-provided-in.js +62 -0
- package/dist/rules/use-lifecycle-interface.js +45 -0
- package/dist/rules/use-pipe-transform-interface.js +47 -0
- package/dist/utils/create-eslint-rule.js +26 -0
- package/package.json +3 -3
- package/dist/configs/base.json +0 -4
- package/dist/configs/ng-cli-compat--formatting-add-on.json +0 -51
- package/dist/configs/ng-cli-compat.json +0 -188
- package/dist/configs/recommended--extra.json +0 -40
- package/dist/eslint-plugin/src/index.d.ts +0 -309
- package/dist/eslint-plugin/src/rules/component-class-suffix.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/component-max-inline-declarations.d.ts +0 -11
- package/dist/eslint-plugin/src/rules/component-selector.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/contextual-decorator.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/contextual-lifecycle.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/directive-class-suffix.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/directive-selector.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/no-attribute-decorator.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-conflicting-lifecycle.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-empty-lifecycle-method.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-forward-ref.d.ts +0 -5
- package/dist/eslint-plugin/src/rules/no-host-metadata-property.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-input-prefix.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-input-rename.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/no-inputs-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-lifecycle-call.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-native.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-on-prefix.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-output-rename.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-outputs-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-pipe-impure.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/no-queries-metadata-property.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/pipe-prefix.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/prefer-on-push-component-change-detection.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/prefer-output-readonly.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/relative-url-prefix.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/require-localize-metadata.d.ts +0 -10
- package/dist/eslint-plugin/src/rules/sort-ngmodule-metadata-arrays.d.ts +0 -9
- package/dist/eslint-plugin/src/rules/use-component-selector.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-component-view-encapsulation.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-injectable-provided-in.d.ts +0 -7
- package/dist/eslint-plugin/src/rules/use-lifecycle-interface.d.ts +0 -4
- package/dist/eslint-plugin/src/rules/use-pipe-transform-interface.d.ts +0 -4
- package/dist/eslint-plugin/src/utils/create-eslint-rule.d.ts +0 -2
package/dist/configs/README.md
CHANGED
|
@@ -2,43 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
These configs exist for your convenience. They contain configuration intended to save you time and effort when configuring your project by disabling rules known to conflict with this repository, or cause issues in Angular codebases.
|
|
4
4
|
|
|
5
|
-
## `
|
|
5
|
+
## `all`
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
As you might infer from the names, these configs exist to most closely match what the Angular CLI used to configure for TSLint and help us reduce a lot of the boilerplate config as part of the TSLint -> ESLint conversion.
|
|
10
|
-
|
|
11
|
-
You are free to remove them or customize them in any way you wish. Over time, we will encourage people more and more to move towards the `recommended` config instead, because this will not be static, it will evolve as recommendations from the Angular Team and community do.
|
|
12
|
-
|
|
13
|
-
Note: The equivalent TSLint config from the Angular CLI = both `ng-cli-compat` + `ng-cli-compat--formatting-add-on`.
|
|
14
|
-
|
|
15
|
-
The reason for separating out the formatting related rules is that we fundamentally believe you should not use a linter for formatting concerns (you should use a dedicated code formatting tool like Prettier instead), and having them in a separate config that is extended from makes it super easy to remove if you choose to.
|
|
7
|
+
Quite simply, this enables all the possible rules from `@angular-eslint/eslint-plugin`. It is unlikely this will be applicable in real-world projects, but some folks find this useful to have as a reference.
|
|
16
8
|
|
|
17
9
|
## `recommended`
|
|
18
10
|
|
|
19
|
-
The recommended set is an **_opinionated_** set of rules that we think you should use because:
|
|
11
|
+
The recommended set is an **_opinionated_** set of Angular-specific rules that we think you should use because:
|
|
20
12
|
|
|
21
|
-
1. They help you adhere to
|
|
13
|
+
1. They help you adhere to Angular best practices.
|
|
22
14
|
2. They help catch probable issue vectors in your code.
|
|
23
15
|
|
|
24
|
-
That being said, it is not the only way to use `@
|
|
16
|
+
That being said, it is not the only way to use `@angular-eslint/eslint-plugin`, nor is it the way that will necessarily work 100% for your project/company. It has been built based off of two main things:
|
|
25
17
|
|
|
26
|
-
1.
|
|
27
|
-
- [TypeScript repo](https://github.com/Microsoft/TypeScript).
|
|
28
|
-
- [TypeScript documentation](https://www.typescriptlang.org/docs/home.html).
|
|
29
|
-
- The style used by many OSS TypeScript projects.
|
|
30
|
-
2. Angular best practices collected and collated from places like:
|
|
18
|
+
1. Angular best practices collected and collated from places like:
|
|
31
19
|
- [Angular repo](https://github.com/angular/angular).
|
|
32
20
|
- [Angular documentation](https://angular.io).
|
|
33
21
|
- Advice from the Angular Team at Google.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
We will not add new rules to the recommended set unless we release a major package version (i.e. it is seen as a breaking change).
|
|
22
|
+
2. The combined state of community contributed rulesets at the time of creation.
|
|
37
23
|
|
|
38
|
-
|
|
24
|
+
It is strongly encouraged to combine the recommended Angular rules with the recommended configs from `typescript-eslint` (https://typescript-eslint.io/linting/configs/#recommended-configurations), and this is what our schematics will generate for you automatically.
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
- `@angular-eslint/eslint-plugin`
|
|
26
|
+
We will not change/add new rules to the recommended set unless we release a major package version (i.e. it is seen as a breaking change).
|
|
42
27
|
|
|
43
28
|
### Altering the recommended set to suit your project
|
|
44
29
|
|
package/dist/configs/all.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1,101 @@
|
|
|
1
|
-
var e=require("@angular-eslint/utils"),t=require("@typescript-eslint/utils");const r=e=>function({name:r,meta:s,defaultOptions:o,create:a}){return{meta:Object.assign(Object.assign({},s),{docs:Object.assign(Object.assign({},s.docs),{url:e(r)})}),defaultOptions:o,create(e){const r=t.ESLintUtils.applyDefault(o,e.options);return a(e,r)}}};r.withoutDocs=t.ESLintUtils.RuleCreator.withoutDocs;const s=r(e=>`https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/docs/rules/${e}.md`);var o=s({name:"contextual-decorator",meta:{type:"suggestion",docs:{description:"Ensures that classes use contextual decorators in its body",recommended:!1},schema:[],messages:{contextualDecorator:'Decorator out of context for "@{{classDecoratorName}}()"'}},defaultOptions:[],create:t=>({"MethodDefinition[kind=/^(get|set|method)$/], PropertyDefinition, TSParameterProperty"(r){!function(t,r){var s;if(null==(s=r.decorators)||!s.length)return;const o=e.ASTUtils.getNearestNodeFrom(r,e.ASTUtils.isClassDeclaration);if(!o)return;const n=e.ASTUtils.getAngularClassDecorator(o);if(n)for(const e of r.decorators)a(t,e,n)}(t,r)}})});function a(t,r,s){const o=e.ASTUtils.getDecoratorName(r);if(!o||!e.ASTUtils.isAngularInnerClassDecorator(o))return;const a=e.ASTUtils.ANGULAR_CLASS_DECORATOR_MAPPER.get(s);null!=a&&a.has(o)||t.report({node:r,messageId:"contextualDecorator",data:{classDecoratorName:s}})}var n=s({name:"component-class-suffix",meta:{type:"suggestion",docs:{description:'Classes decorated with @Component must have suffix "Component" (or custom) in their name. See more at https://angular.io/styleguide#style-02-03',recommended:"error"},schema:[{type:"object",properties:{suffixes:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{componentClassSuffix:"Component class names should end with one of these suffixes: {{suffixes}} (https://angular.io/styleguide#style-02-03)"}},defaultOptions:[{suffixes:["Component"]}],create:(t,[{suffixes:r}])=>({[e.Selectors.COMPONENT_CLASS_DECORATOR](s){const o=s.parent,a=e.ASTUtils.getClassName(o);a&&r.some(e=>a.endsWith(e))||t.report({node:o.id?o.id:o,messageId:"componentClassSuffix",data:{suffixes:e.toHumanReadableText(r)}})}})});const i=/\r\n|\r|\n/;function l(t){return e.ASTUtils.isTemplateLiteral(t)?t.quasis[0].value.raw.trim().split(i).length:e.ASTUtils.isLiteral(t)?t.raw.trim().split(i).length:0}var c=s({name:"component-max-inline-declarations",meta:{type:"suggestion",docs:{description:"Enforces a maximum number of lines in inline template, styles and animations. See more at https://angular.io/guide/styleguide#style-05-04",recommended:!1},schema:[{type:"object",properties:{template:{minimum:0,type:"number"},styles:{minimum:0,type:"number"},animations:{minimum:0,type:"number"}},additionalProperties:!1}],messages:{componentMaxInlineDeclarations:"`{{propertyType}}` has too many lines ({{lineCount}}). Maximum allowed is {{max}} (https://angular.io/guide/styleguide#style-05-04)"}},defaultOptions:[{template:3,styles:3,animations:15}],create:(t,[{template:r=3,styles:s=3,animations:o=15}])=>({[`${e.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='template']`]({value:e}){const s=l(e);s<=r||t.report({node:e,messageId:"componentMaxInlineDeclarations",data:{lineCount:s,max:r,propertyType:"template"}})},[`${e.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styles']`]({value:r}){if(!e.ASTUtils.isArrayExpression(r))return;const o=r.elements.reduce((e,t)=>e+(t?l(t):0),0);o<=s||t.report({node:r,messageId:"componentMaxInlineDeclarations",data:{lineCount:o,max:s,propertyType:"styles"}})},[`${e.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='animations']`]({value:r}){if(!e.ASTUtils.isArrayExpression(r)||0===r.elements.length)return;const s=Math.max(r.loc.end.line-r.loc.start.line-2,1);s<=o||t.report({node:r,messageId:"componentMaxInlineDeclarations",data:{lineCount:s,max:o,propertyType:"animations"}})}})});const p="https://angular.io/guide/styleguide#style-05-02";var u=s({name:"component-selector",meta:{type:"suggestion",docs:{description:`Component selectors should follow given naming rules. See more at https://angular.io/guide/styleguide#style-02-07, ${p}\n and https://angular.io/guide/styleguide#style-05-03.`,recommended:!1},schema:[{type:"object",properties:{type:{oneOf:[{type:"string"},{type:"array",items:{enum:[e.SelectorUtils.OPTION_TYPE_ELEMENT,e.SelectorUtils.OPTION_TYPE_ATTRIBUTE]}}]},prefix:{oneOf:[{type:"string"},{type:"array"}]},style:{type:"string",enum:[e.ASTUtils.OPTION_STYLE_CAMEL_CASE,e.ASTUtils.OPTION_STYLE_KEBAB_CASE]}},additionalProperties:!1}],messages:{prefixFailure:"The selector should start with one of these prefixes: {{prefix}} (https://angular.io/guide/styleguide#style-02-07)",styleFailure:`The selector should be {{style}} (${p})`,styleAndPrefixFailure:`The selector should be {{style}} and start with one of these prefixes: {{prefix}} (${p} and https://angular.io/guide/styleguide#style-02-07)`,typeFailure:"The selector should be used as an {{type}} (https://angular.io/guide/styleguide#style-05-03)",shadowDomEncapsulatedStyleFailure:`The selector of a ShadowDom-encapsulated component should be \`${e.ASTUtils.OPTION_STYLE_KEBAB_CASE}\` (https://github.com/angular-eslint/angular-eslint/issues/534)`}},defaultOptions:[{type:"",prefix:"",style:""}],create:(r,[{type:s,prefix:o,style:a}])=>({[e.Selectors.COMPONENT_CLASS_DECORATOR](n){const i=e.ASTUtils.getDecoratorPropertyValue(n,"selector");if(!i)return;if(!e.SelectorUtils.checkValidOptions(s,o,a))return;const l=a!==e.ASTUtils.OPTION_STYLE_KEBAB_CASE&&function(r){const s=e.ASTUtils.getDecoratorPropertyValue(r,"encapsulation");return s&&e.ASTUtils.isMemberExpression(s)&&t.ASTUtils.isIdentifier(s.object)&&"ViewEncapsulation"===s.object.name&&t.ASTUtils.isIdentifier(s.property)&&"ShadowDom"===s.property.name}(n)?e.ASTUtils.OPTION_STYLE_KEBAB_CASE:a,c=e.SelectorUtils.checkSelector(i,s,e.arrayify(o),l);null!==c&&(c.hasExpectedType?c.hasExpectedStyle?c.hasExpectedPrefix||e.SelectorUtils.reportPrefixError(i,o,r):a===l?c.hasExpectedPrefix?e.SelectorUtils.reportStyleError(i,a,r):e.SelectorUtils.reportStyleAndPrefixError(i,a,o,r):r.report({node:i,messageId:"shadowDomEncapsulatedStyleFailure"}):e.SelectorUtils.reportTypeError(i,s,r))}})}),d=s({name:"contextual-lifecycle",meta:{type:"problem",docs:{description:"Ensures that lifecycle methods are used in a correct context",recommended:"error"},schema:[],messages:{contextualLifecycle:"Angular will not invoke the `{{methodName}}` lifecycle method within `@{{classDecoratorName}}()` classes"}},defaultOptions:[],create(t){function r({parent:r},s){const o=r,a=e.ASTUtils.ANGULAR_CLASS_DECORATOR_LIFECYCLE_METHOD_MAPPER.get(s),n=e.ASTUtils.getDeclaredMethods(o);for(const r of n){const o=e.ASTUtils.getMethodName(r);!o||!e.ASTUtils.isAngularLifecycleMethod(o)||null!=a&&a.has(o)||t.report({node:r.key,messageId:"contextualLifecycle",data:{classDecoratorName:s,methodName:o}})}}return{[e.Selectors.COMPONENT_CLASS_DECORATOR](t){r(t,e.ASTUtils.AngularClassDecorators.Component)},[e.Selectors.DIRECTIVE_CLASS_DECORATOR](t){r(t,e.ASTUtils.AngularClassDecorators.Directive)},[e.Selectors.INJECTABLE_CLASS_DECORATOR](t){r(t,e.ASTUtils.AngularClassDecorators.Injectable)},[e.Selectors.MODULE_CLASS_DECORATOR](t){r(t,e.ASTUtils.AngularClassDecorators.NgModule)},[e.Selectors.PIPE_CLASS_DECORATOR](t){r(t,e.ASTUtils.AngularClassDecorators.Pipe)}}}}),m=s({name:"directive-class-suffix",meta:{type:"suggestion",docs:{description:'Classes decorated with @Directive must have suffix "Directive" (or custom) in their name. See more at https://angular.io/styleguide#style-02-03',recommended:"error"},schema:[{type:"object",properties:{suffixes:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{directiveClassSuffix:"Directive class names should end with one of these suffixes: {{suffixes}} (https://angular.io/styleguide#style-02-03)"}},defaultOptions:[{suffixes:["Directive"]}],create:(t,[{suffixes:r}])=>({[e.Selectors.DIRECTIVE_CLASS_DECORATOR](s){if(!e.ASTUtils.getDecoratorPropertyValue(s,"selector"))return;const o=s.parent,a=e.ASTUtils.getClassName(o),n=e.ASTUtils.getDeclaredInterfaceNames(o).some(e=>e.endsWith("Validator")),i=r.concat(n?"Validator":[]);var l;a&&i.some(e=>a.endsWith(e))||t.report({node:null!=(l=o.id)?l:o,messageId:"directiveClassSuffix",data:{suffixes:e.toHumanReadableText(i)}})}})}),g=s({name:"directive-selector",meta:{type:"suggestion",docs:{description:"Directive selectors should follow given naming rules. See more at https://angular.io/guide/styleguide#style-02-06 and https://angular.io/guide/styleguide#style-02-08.",recommended:!1},schema:[{type:"object",properties:{type:{oneOf:[{type:"string"},{type:"array",items:{enum:[e.SelectorUtils.OPTION_TYPE_ELEMENT,e.SelectorUtils.OPTION_TYPE_ATTRIBUTE]}}]},prefix:{oneOf:[{type:"string"},{type:"array"}]},style:{type:"string",enum:[e.ASTUtils.OPTION_STYLE_CAMEL_CASE,e.ASTUtils.OPTION_STYLE_KEBAB_CASE]}},additionalProperties:!1}],messages:{prefixFailure:"The selector should start with one of these prefixes: {{prefix}} (https://angular.io/guide/styleguide#style-02-08)",styleFailure:"The selector should be {{style}} (https://angular.io/guide/styleguide#style-02-06)",typeFailure:"The selector should be used as an {{type}} (https://angular.io/guide/styleguide#style-02-06)"}},defaultOptions:[{type:"",prefix:"",style:""}],create:(t,[{type:r,prefix:s,style:o}])=>({[e.Selectors.DIRECTIVE_CLASS_DECORATOR](a){const n=e.ASTUtils.getDecoratorPropertyValue(a,"selector");if(!n)return;if(!e.SelectorUtils.checkValidOptions(r,s,o))return;const i=e.SelectorUtils.checkSelector(n,r,e.arrayify(s),o);null!==i&&(i.hasExpectedType?i.hasExpectedStyle?i.hasExpectedPrefix||e.SelectorUtils.reportPrefixError(n,s,t):e.SelectorUtils.reportStyleError(n,o,t):e.SelectorUtils.reportTypeError(n,r,t))}})}),f=s({name:"no-attribute-decorator",meta:{type:"problem",docs:{description:"The @Attribute decorator is used to obtain a single value for an attribute. This is a much less common use-case than getting a stream of values (using @Input), so often the @Attribute decorator is mistakenly used when @Input was what was intended. This rule disallows usage of @Attribute decorator altogether in order to prevent these mistakes.",recommended:!1},schema:[],messages:{noAttributeDecorator:"@Attribute can only obtain a single value and is rarely what is required. Use @Input instead to retrieve a stream of values."}},defaultOptions:[],create:e=>({'ClassDeclaration MethodDefinition[key.name="constructor"] Decorator[expression.callee.name="Attribute"]'(t){e.report({node:t,messageId:"noAttributeDecorator"})}})});const y=[e.ASTUtils.AngularLifecycleInterfaces.DoCheck,e.ASTUtils.AngularLifecycleInterfaces.OnChanges],h=[e.ASTUtils.AngularLifecycleMethods.ngDoCheck,e.ASTUtils.AngularLifecycleMethods.ngOnChanges];var S=s({name:"no-conflicting-lifecycle",meta:{type:"suggestion",docs:{description:"Ensures that directives not implement conflicting lifecycle interfaces.",recommended:!1},schema:[],messages:{noConflictingLifecycleInterface:`Implementing ${e.ASTUtils.AngularLifecycleInterfaces.DoCheck} and ${e.ASTUtils.AngularLifecycleInterfaces.OnChanges} in a class is not recommended`,noConflictingLifecycleMethod:`Declaring ${e.ASTUtils.AngularLifecycleMethods.ngDoCheck} and ${e.ASTUtils.AngularLifecycleMethods.ngOnChanges} method in a class is not recommended`}},defaultOptions:[],create:r=>({ClassDeclaration(s){(t=>{const s=e.ASTUtils.getDeclaredAngularLifecycleInterfaces(t);if(!y.every(e=>s.includes(e)))return;const o=e.ASTUtils.getInterfaces(t).filter(t=>{const r=e.ASTUtils.getInterfaceName(t);return r&&e.ASTUtils.isAngularLifecycleInterface(r)});for(const e of o)r.report({node:e,messageId:"noConflictingLifecycleInterface"})})(s),(s=>{const o=e.ASTUtils.getDeclaredAngularLifecycleMethods(s);if(!h.every(e=>o.includes(e)))return;const a=e.ASTUtils.getDeclaredMethods(s).filter(r=>t.ASTUtils.isIdentifier(r.key)&&e.ASTUtils.isAngularLifecycleMethod(r.key.name));for(const e of a)r.report({node:e,messageId:"noConflictingLifecycleMethod"})})(s)}})}),T=s({name:"no-forward-ref",meta:{type:"suggestion",docs:{description:"Disallows usage of `forwardRef` references for DI",recommended:!1},schema:[],messages:{noForwardRef:"Avoid using `forwardRef`"}},defaultOptions:[],create:e=>({'CallExpression[callee.type="Identifier"][callee.name="forwardRef"]'(t){e.report({node:t,messageId:"noForwardRef"})}})});const A={allowStatic:!1};var x=s({name:"no-host-metadata-property",meta:{type:"suggestion",docs:{description:"Disallows usage of the `host` metadata property. See more at https://angular.io/styleguide#style-06-03",recommended:"error"},schema:[{type:"object",properties:{allowStatic:{type:"boolean",default:A.allowStatic}},additionalProperties:!1}],messages:{noHostMetadataProperty:`Use @${e.ASTUtils.AngularInnerClassDecorators.HostBinding} or @${e.ASTUtils.AngularInnerClassDecorators.HostListener} rather than the \`host\` metadata property (https://angular.io/styleguide#style-06-03)`}},defaultOptions:[A],create:(t,[{allowStatic:r}])=>({[`${e.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR} Property[key.name="host"]`](s){(r&&e.ASTUtils.isObjectExpression(s.value)?s.value.properties.filter(O):[s]).forEach(e=>{t.report({node:e,messageId:"noHostMetadataProperty"})})}})});function O(r){return e.ASTUtils.isProperty(r)&&!function(r){return!r.computed&&(t.ASTUtils.isIdentifier(r.key)||e.ASTUtils.isStringLiteral(r.key)&&function({0:e}){return e.toLowerCase()!==e.toUpperCase()}(r.key.value))}(r)&&!function(t){return e.ASTUtils.isStringLiteral(t.value)&&""===t.value.value}(r)}var E=s({name:"no-input-prefix",meta:{type:"suggestion",docs:{description:"Ensures that input bindings, including aliases, are not named or prefixed by the configured disallowed prefixes",recommended:!1},schema:[{type:"object",properties:{prefixes:{type:"array",items:{type:"string"}}},additionalProperties:!1}],messages:{noInputPrefix:"Input bindings, including aliases, should not be named, nor prefixed by {{prefixes}}"}},defaultOptions:[{prefixes:[]}],create:(t,[{prefixes:r}])=>({[[e.Selectors.INPUTS_METADATA_PROPERTY_LITERAL,e.Selectors.INPUT_ALIAS,e.Selectors.INPUT_PROPERTY_OR_SETTER].join(",")](s){const[o,a]=e.ASTUtils.getRawText(s).replace(/\s/g,"").split(":");r.some(e=>function(e,t,r){const s=RegExp(`^${e}(([^a-z])|(?=$))`);return s.test(t)||s.test(r)}(e,o,a))&&t.report({node:s,messageId:"noInputPrefix",data:{prefixes:e.toHumanReadableText(r)}})}})}),I=s({name:"no-input-rename",meta:{type:"suggestion",docs:{description:"Ensures that input bindings are not aliased",recommended:"error"},fixable:"code",hasSuggestions:!0,schema:[{type:"object",properties:{allowedNames:{type:"array",items:{type:"string"},description:"A list with allowed input names",uniqueItems:!0}},additionalProperties:!1}],messages:{noInputRename:"Input bindings should not be aliased (https://angular.io/guide/styleguide#style-05-13)",suggestRemoveAliasName:"Remove alias name",suggestReplaceOriginalNameWithAliasName:"Remove alias name and use it as the original name"}},defaultOptions:[{allowedNames:[]}],create(r,[{allowedNames:s=[]}]){let o=new Set;const a=e.getAriaAttributeKeys();let n;return{[e.Selectors.COMPONENT_OR_DIRECTIVE_SELECTOR_LITERAL](t){const r=e.ASTUtils.getRawText(t),s=r.match(/\[(.*?)\]/);s&&(n=s[1]),o=new Set(e.withoutBracketsAndWhitespaces(r).split(","))},[e.Selectors.INPUT_ALIAS](i){const l=e.ASTUtils.getNearestNodeFrom(i,e.ASTUtils.isPropertyOrMethodDefinition);if(!l||!t.ASTUtils.isIdentifier(l.key))return;const c=e.ASTUtils.getRawText(i),p=e.ASTUtils.getRawText(l.key);s.includes(c)||a.has(c)&&p===e.kebabToCamelCase(c)||(c===p?r.report({node:i,messageId:"noInputRename",fix:e=>e.remove(i)}):C(o,p,c,n)||r.report({node:i,messageId:"noInputRename",suggest:[{messageId:"suggestRemoveAliasName",fix:e=>e.remove(i)},{messageId:"suggestReplaceOriginalNameWithAliasName",fix:e=>[e.remove(i),e.replaceText(l.key,c.includes("-")?`'${c}'`:c)]}]}))},[e.Selectors.INPUTS_METADATA_PROPERTY_LITERAL](i){var l,c,p,u;const d=null==(l=i.parent)||null==(c=l.parent)||null==(p=c.parent)||null==(u=p.parent)?void 0:u.parent;if(d&&e.ASTUtils.isProperty(d)){const r="hostDirectives";if(e.ASTUtils.isLiteral(d.key)&&d.key.value===r||t.ASTUtils.isIdentifier(d.key)&&d.key.name===r)return}const[m,g]=e.withoutBracketsAndWhitespaces(e.ASTUtils.getRawText(i)).split(":");!g||s.includes(g)||a.has(g)&&m===e.kebabToCamelCase(g)||(g===m?r.report({node:i,messageId:"noInputRename",fix:t=>t.replaceText(i,e.ASTUtils.getReplacementText(i,m))}):C(o,m,g,n)||r.report({node:i,messageId:"noInputRename",suggest:[["suggestRemoveAliasName",m],["suggestReplaceOriginalNameWithAliasName",g]].map(([t,r])=>({messageId:t,fix:t=>t.replaceText(i,e.ASTUtils.getReplacementText(i,r))}))}))},"ClassDeclaration:exit"(){o=new Set}}}});function C(t,r,s,o){return[...t].some(t=>t===s||o===s||function(t,r){return`${t}${e.capitalize(r)}`}(t,r)===s)}var R=s({name:"no-inputs-metadata-property",meta:{type:"suggestion",docs:{description:"Disallows usage of the `inputs` metadata property. See more at https://angular.io/styleguide#style-05-12",recommended:"error"},schema:[],messages:{noInputsMetadataProperty:"Use `@Input` rather than the `inputs` metadata property (https://angular.io/styleguide#style-05-12)"}},defaultOptions:[],create:r=>({[`${e.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR} ${e.Selectors.metadataProperty("inputs")}`](s){var o,a;const n=null==(o=s.parent)||null==(a=o.parent)?void 0:a.parent;if(n&&e.ASTUtils.isProperty(n)){const r="hostDirectives";if(e.ASTUtils.isLiteral(n.key)&&n.key.value===r||t.ASTUtils.isIdentifier(n.key)&&n.key.name===r)return}r.report({node:s,messageId:"noInputsMetadataProperty"})}})}),U=s({name:"no-lifecycle-call",meta:{type:"suggestion",docs:{description:"Disallows explicit calls to lifecycle methods",recommended:!1},schema:[],messages:{noLifecycleCall:"Avoid explicit calls to lifecycle methods"}},defaultOptions:[],create:r=>({[`ClassDeclaration CallExpression > MemberExpression[property.name=${e.toPattern([...e.ASTUtils.ANGULAR_LIFECYCLE_METHODS])}]`]:s=>{const o=e.ASTUtils.getNearestNodeFrom(s,e.ASTUtils.isClassDeclaration);!o||!e.ASTUtils.getAngularClassDecorator(o)||e.ASTUtils.isSuper(s.object)&&function(r){const s=e.ASTUtils.getNearestNodeFrom(r,e.ASTUtils.isMethodDefinition);return Boolean(s&&function({property:e},{key:r}){return t.ASTUtils.isIdentifier(e)&&t.ASTUtils.isIdentifier(r)&&e.name===r.name}(r,s))}(s)||r.report({node:s.parent,messageId:"noLifecycleCall"})}})}),v=s({name:"no-output-native",meta:{type:"suggestion",docs:{description:"Ensures that output bindings, including aliases, are not named as standard DOM events",recommended:"error"},schema:[],messages:{noOutputNative:"Output bindings, including aliases, should not be named as standard DOM events"}},defaultOptions:[],create(t){const r=e.getNativeEventNames();return{[[e.Selectors.OUTPUTS_METADATA_PROPERTY_LITERAL,e.Selectors.OUTPUT_ALIAS,e.Selectors.OUTPUT_PROPERTY_OR_GETTER].join(",")](s){const[o,a]=e.ASTUtils.getRawText(s).replace(/\s/g,"").split(":");(r.has(o)||r.has(a))&&t.report({node:s,messageId:"noOutputNative"})}}}}),P=s({name:"no-output-on-prefix",meta:{type:"suggestion",docs:{description:'Ensures that output bindings, including aliases, are not named "on", nor prefixed with it. See more at https://angular.io/guide/styleguide#style-05-16',recommended:"error"},schema:[],messages:{noOutputOnPrefix:'Output bindings, including aliases, should not be named "on", nor prefixed with it (https://angular.io/guide/styleguide#style-05-16)'}},defaultOptions:[],create(t){const r=/^on(([^a-z])|(?=$))/;return{[[e.Selectors.OUTPUTS_METADATA_PROPERTY_LITERAL,e.Selectors.OUTPUT_ALIAS,e.Selectors.OUTPUT_PROPERTY_OR_GETTER].join(",")](s){const[o,a]=e.ASTUtils.getRawText(s).replace(/\s/g,"").split(":");(r.test(o)||r.test(a))&&t.report({node:s,messageId:"noOutputOnPrefix"})}}}}),D=s({name:"no-output-rename",meta:{type:"suggestion",docs:{description:"Ensures that output bindings are not aliased",recommended:"error"},fixable:"code",hasSuggestions:!0,schema:[],messages:{noOutputRename:"Output bindings should not be aliased (https://angular.io/guide/styleguide#style-05-13)",suggestRemoveAliasName:"Remove alias name",suggestReplaceOriginalNameWithAliasName:"Remove alias name and use it as the original name"}},defaultOptions:[],create(r){let s=new Set;return{[e.Selectors.COMPONENT_OR_DIRECTIVE_SELECTOR_LITERAL](t){s=new Set(e.withoutBracketsAndWhitespaces(e.ASTUtils.getRawText(t)).split(","))},[e.Selectors.OUTPUT_ALIAS](o){const a=e.ASTUtils.getNearestNodeFrom(o,e.ASTUtils.isPropertyOrMethodDefinition);if(!a||!t.ASTUtils.isIdentifier(a.key))return;const n=e.ASTUtils.getRawText(o),i=e.ASTUtils.getRawText(a.key);n===i?r.report({node:o,messageId:"noOutputRename",fix:e=>e.remove(o)}):b(s,i,n)||r.report({node:o,messageId:"noOutputRename",suggest:[{messageId:"suggestRemoveAliasName",fix:e=>e.remove(o)},{messageId:"suggestReplaceOriginalNameWithAliasName",fix:e=>[e.remove(o),e.replaceText(a.key,n.includes("-")?`'${n}'`:n)]}]})},[e.Selectors.OUTPUTS_METADATA_PROPERTY_LITERAL](t){const[o,a]=e.withoutBracketsAndWhitespaces(e.ASTUtils.getRawText(t)).split(":");a&&(a===o?r.report({node:t,messageId:"noOutputRename",fix:r=>r.replaceText(t,e.ASTUtils.getReplacementText(t,o))}):b(s,o,a)||r.report({node:t,messageId:"noOutputRename",suggest:[["suggestRemoveAliasName",o],["suggestReplaceOriginalNameWithAliasName",a]].map(([r,s])=>({messageId:r,fix:r=>r.replaceText(t,e.ASTUtils.getReplacementText(t,s))}))}))},"ClassDeclaration:exit"(){s=new Set}}}});function b(t,r,s){return[...t].some(t=>t===s||function(t,r){return`${t}${e.capitalize(r)}`}(t,r)===s)}var N=s({name:"no-outputs-metadata-property",meta:{type:"suggestion",docs:{description:"Disallows usage of the `outputs` metadata property. See more at https://angular.io/styleguide#style-05-12",recommended:"error"},schema:[],messages:{noOutputsMetadataProperty:"Use `@Output` rather than the `outputs` metadata property (https://angular.io/styleguide#style-05-12)"}},defaultOptions:[],create:t=>({[`${e.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR} ${e.Selectors.metadataProperty("outputs")}`](e){t.report({node:e,messageId:"noOutputsMetadataProperty"})}})}),_=s({name:"no-pipe-impure",meta:{type:"suggestion",docs:{description:"Disallows the declaration of impure pipes",recommended:!1},hasSuggestions:!0,schema:[],messages:{noPipeImpure:"Impure pipes should be avoided because they are invoked on each change-detection cycle",suggestRemovePipeImpure:"Remove `pure` property"}},defaultOptions:[],create(t){const r=t.getSourceCode();return{[`${e.Selectors.PIPE_CLASS_DECORATOR} ${e.Selectors.metadataProperty("pure")}:matches([value.value=false], [value.operator='!'][value.argument.value=true])`](s){t.report({node:s,messageId:"noPipeImpure",suggest:[{messageId:"suggestRemovePipeImpure",fix:t=>e.RuleFixes.getNodeToCommaRemoveFix(r,s,t)}]})}}}}),L=s({name:"no-queries-metadata-property",meta:{type:"suggestion",docs:{description:"Disallows usage of the `queries` metadata property. See more at https://angular.io/styleguide#style-05-12.",recommended:!1},schema:[],messages:{noQueriesMetadataProperty:`Use @${e.ASTUtils.AngularInnerClassDecorators.Output} rather than the \`queries\` metadata property (https://angular.io/styleguide#style-05-12)`}},defaultOptions:[],create:t=>({[e.Selectors.COMPONENT_OR_DIRECTIVE_CLASS_DECORATOR](r){const s=e.ASTUtils.getDecoratorPropertyValue(r,"queries");s&&t.report({node:s.parent,messageId:"noQueriesMetadataProperty"})}})}),w=s({name:"no-empty-lifecycle-method",meta:{type:"suggestion",docs:{description:"Disallows declaring empty lifecycle methods",recommended:"error"},hasSuggestions:!0,schema:[],messages:{noEmptyLifecycleMethod:"Lifecycle methods should not be empty",suggestRemoveLifecycleMethod:"Remove lifecycle method"}},defaultOptions:[],create(t){const r=t.getSourceCode(),s=e.toPattern([...e.ASTUtils.ANGULAR_LIFECYCLE_METHODS]);return{[`ClassDeclaration:has(Decorator[expression.callee.name=/^(Component|Directive|Injectable|NgModule|Pipe)$/]) > ClassBody > ${e.Selectors.methodDefinition(s)}[value.body.body.length=0]`](s){t.report({node:s,messageId:"noEmptyLifecycleMethod",suggest:[{messageId:"suggestRemoveLifecycleMethod",fix:t=>{var o;const a=null!=(o=e.ASTUtils.getImportDeclarations(s,"@angular/core"))?o:[],n=e.ASTUtils.getRawText(s).replace(/^ng+/,""),i=function(e,t){return e.split(" ").filter(e=>function(e){return e.replace(/[\W]/g,"")}(e)===t).length}(r.getText(),n);return[t.remove(s),e.RuleFixes.getImplementsRemoveFix(r,s.parent.parent,n,t),i<=2?e.RuleFixes.getImportRemoveFix(r,a,n,t):null].filter(e.isNotNullOrUndefined)}}]})}}}});const M="prefer-on-push-component-change-detection",j="ChangeDetectionStrategy.OnPush";var k=s({name:M,meta:{type:"suggestion",docs:{description:`Ensures component's \`changeDetection\` is set to \`${j}\``,recommended:!1},hasSuggestions:!0,schema:[],messages:{preferOnPushComponentChangeDetection:`The component's \`changeDetection\` value should be set to \`${j}\``,suggestAddChangeDetectionOnPush:`Add \`${j}\``}},defaultOptions:[],create(t){const r=e.Selectors.metadataProperty("changeDetection");return{[[`${e.Selectors.COMPONENT_CLASS_DECORATOR}:matches([expression.arguments.length=0], [expression.arguments.0.type='ObjectExpression']:not(:has(${r})))`,`${e.Selectors.COMPONENT_CLASS_DECORATOR} > CallExpression > ObjectExpression > ${r}:matches([value.type='Identifier'][value.name='undefined'], [value.object.name='ChangeDetectionStrategy'][value.property.name!='OnPush'])`].join(",")](r){t.report({node:$(r),messageId:"preferOnPushComponentChangeDetection",suggest:[{messageId:"suggestAddChangeDetectionOnPush",fix:t=>e.ASTUtils.isProperty(r)?[e.RuleFixes.getImportAddFix({fixer:t,importName:"ChangeDetectionStrategy",moduleName:"@angular/core",node:r.parent.parent.parent.parent}),e.ASTUtils.isMemberExpression(r.value)?t.replaceText(r.value.property,"OnPush"):t.replaceText(r.value,j)].filter(e.isNotNullOrUndefined):[e.RuleFixes.getImportAddFix({fixer:t,importName:"ChangeDetectionStrategy",moduleName:"@angular/core",node:r.parent}),e.RuleFixes.getDecoratorPropertyAddFix(r,t,`changeDetection: ${j}`)].filter(e.isNotNullOrUndefined)}]})}}}});function $(t){return e.ASTUtils.isProperty(t)?e.ASTUtils.isMemberExpression(t.value)?t.value.property:t.value:t}var F=s({name:"prefer-output-readonly",meta:{type:"suggestion",docs:{description:"Prefer to declare `@Output` as `readonly` since they are not supposed to be reassigned",recommended:!1},hasSuggestions:!0,schema:[],messages:{preferOutputReadonly:"Prefer to declare `@Output` as `readonly` since they are not supposed to be reassigned",suggestAddReadonlyModifier:"Add `readonly` modifier"}},defaultOptions:[],create:t=>({[`PropertyDefinition:not([readonly]) > ${e.Selectors.OUTPUT_DECORATOR}`]({parent:{key:e}}){t.report({node:e,messageId:"preferOutputReadonly",suggest:[{messageId:"suggestAddReadonlyModifier",fix:t=>t.insertTextBefore(e,"readonly ")}]})}})});const V=/^\.\.?\/.+/;var B=s({name:"relative-url-prefix",meta:{type:"suggestion",docs:{description:"The ./ and ../ prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix. See more at https://angular.io/styleguide#style-05-04",recommended:!1},schema:[],messages:{relativeUrlPrefix:"The ./ and ../ prefix is standard syntax for relative URLs. (https://angular.io/styleguide#style-05-04)"}},defaultOptions:[],create:t=>({[`${e.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='templateUrl']`]({value:e}){q(e)&&t.report({node:e,messageId:"relativeUrlPrefix"})},[`${e.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styleUrls']`]({value:r}){e.ASTUtils.isArrayExpression(r)&&r.elements.filter(q).forEach(e=>{e&&t.report({node:e,messageId:"relativeUrlPrefix"})})}})});function q(t){return!(!t||e.ASTUtils.isStringLiteral(t)&&V.test(t.value))}var Y=s({name:"sort-ngmodule-metadata-arrays",meta:{type:"suggestion",docs:{description:"Ensures ASC alphabetical order for `NgModule` metadata arrays for easy visual scanning",recommended:!1},fixable:"code",schema:[{type:"object",properties:{locale:{type:"string",description:"A string with a BCP 47 language tag.",default:"en-US"}},additionalProperties:!1}],messages:{sortNgmoduleMetadataArrays:"`NgModule` metadata arrays should be sorted in ASC alphabetical order"}},defaultOptions:[{locale:"en-US"}],create:(r,[{locale:s}])=>({[`${e.Selectors.MODULE_CLASS_DECORATOR} Property[key.name!="deps"] > ArrayExpression`]({elements:e}){const o=e.filter(t.ASTUtils.isIdentifier).map((e,t,r)=>[e,r[t+1]]).find(([e,t])=>t&&1===e.name.localeCompare(t.name,s));if(!o)return;const[a,n]=o;r.report({node:n,messageId:"sortNgmoduleMetadataArrays",fix:e=>[e.replaceText(a,n.name),e.replaceText(n,a.name)]})}})}),W=s({name:"use-component-selector",meta:{type:"suggestion",docs:{description:"Component selector must be declared",recommended:!1},schema:[],messages:{useComponentSelector:"The selector of the component is mandatory"}},defaultOptions:[],create:t=>({[e.Selectors.COMPONENT_CLASS_DECORATOR](r){const s=e.ASTUtils.getDecoratorPropertyValue(r,"selector");s&&e.ASTUtils.isStringLiteral(s)&&s.value.length||t.report({node:r,messageId:"useComponentSelector"})}})}),H=s({name:"use-component-view-encapsulation",meta:{type:"suggestion",docs:{description:"Disallows using `ViewEncapsulation.None`",recommended:!1},hasSuggestions:!0,schema:[],messages:{useComponentViewEncapsulation:"Using `ViewEncapsulation.None` makes your styles global, which may have an unintended effect",suggestRemoveViewEncapsulationNone:"Remove `ViewEncapsulation.None`"}},defaultOptions:[],create(t){const r=t.getSourceCode();return{[`${e.Selectors.COMPONENT_CLASS_DECORATOR} ${e.Selectors.metadataProperty("encapsulation")} > MemberExpression[object.name='ViewEncapsulation'] > Identifier[name='None']`](s){t.report({node:s,messageId:"useComponentViewEncapsulation",suggest:[{messageId:"suggestRemoveViewEncapsulationNone",fix:t=>{var o;const a=null!=(o=e.ASTUtils.getImportDeclarations(s,"@angular/core"))?o:[];return[e.RuleFixes.getNodeToCommaRemoveFix(r,s.parent.parent,t),e.RuleFixes.getImportRemoveFix(r,a,"ViewEncapsulation",t)].filter(e.isNotNullOrUndefined)}}]})}}}}),G=s({name:"use-injectable-provided-in",meta:{type:"suggestion",docs:{description:"Using the `providedIn` property makes `Injectables` tree-shakable",recommended:!1},hasSuggestions:!0,schema:[{type:"object",properties:{ignoreClassNamePattern:{type:"string"}},additionalProperties:!1}],messages:{useInjectableProvidedIn:"The `providedIn` property is mandatory for `Injectables`",suggestInjector:"Use `providedIn: '{{injector}}'`"}},defaultOptions:[{}],create(t,[{ignoreClassNamePattern:r}]){const s=`ClassDeclaration:not([id.name=${r}]):not(:has(TSClassImplements:matches([expression.property.name='HttpInterceptor'], [expression.name='HttpInterceptor']))) > Decorator[expression.callee.name="Injectable"]`,o=e.Selectors.metadataProperty("providedIn");return{[[`${s}:matches([expression.arguments.length=0], [expression.arguments.0.type='ObjectExpression']:not(:has(${o})))`,`${s} ${o}:matches([value.type='Identifier'][value.name='undefined'], [value.type='Literal'][value.raw='null'])`].join(",")](r){t.report({node:e.ASTUtils.isProperty(r)?r.value:r,messageId:"useInjectableProvidedIn",suggest:["any","platform","root"].map(t=>({messageId:"suggestInjector",fix:s=>{var o;return e.ASTUtils.isProperty(r)?s.replaceText(r.value,`'${t}'`):null!=(o=e.RuleFixes.getDecoratorPropertyAddFix(r,s,`providedIn: '${t}'`))?o:[]},data:{injector:t}}))})}}}}),K=s({name:"use-lifecycle-interface",meta:{type:"suggestion",docs:{description:"Ensures that classes implement lifecycle interfaces corresponding to the declared lifecycle methods. See more at https://angular.io/styleguide#style-09-01",recommended:"warn"},schema:[],messages:{useLifecycleInterface:"Lifecycle interface '{{interfaceName}}' should be implemented for method '{{methodName}}'. (https://angular.io/styleguide#style-09-01)"}},defaultOptions:[],create:t=>({[`MethodDefinition[key.name=${e.toPattern([...e.ASTUtils.ANGULAR_LIFECYCLE_METHODS])}]`]({key:r,parent:{parent:s}}){if(!e.ASTUtils.getAngularClassDecorator(s))return;const o=e.ASTUtils.getDeclaredAngularLifecycleInterfaces(s),a=r.name,n=e.ASTUtils.getLifecycleInterfaceByMethodName(a);o.includes(e.ASTUtils.AngularLifecycleInterfaces[n])||t.report({node:r,messageId:"useLifecycleInterface",data:{interfaceName:n,methodName:a}})}})}),z=s({name:"use-pipe-transform-interface",meta:{type:"suggestion",docs:{description:"Ensures that `Pipes` implement `PipeTransform` interface",recommended:"error"},fixable:"code",schema:[],messages:{usePipeTransformInterface:"Pipes should implement `PipeTransform` interface"}},defaultOptions:[],create:t=>({"ClassDeclaration:not(:has(TSClassImplements:matches([expression.name='PipeTransform'], [expression.property.name='PipeTransform']))) > Decorator[expression.callee.name='Pipe']"({parent:r}){var s;t.report({node:null!=(s=r.id)?s:r,messageId:"usePipeTransformInterface",fix:t=>{const{implementsNodeReplace:s,implementsTextReplace:o}=e.RuleFixes.getImplementsSchemaFixer(r,"PipeTransform");return[e.RuleFixes.getImportAddFix({compatibleWithTypeOnlyImport:!0,fixer:t,importName:"PipeTransform",moduleName:"@angular/core",node:r}),t.insertTextAfter(s,o)].filter(e.isNotNullOrUndefined)}})}})}),Q=s({name:"pipe-prefix",meta:{type:"suggestion",docs:{description:"Enforce consistent prefix for pipes.",recommended:!1},schema:[{type:"object",properties:{prefixes:{type:"array",items:{type:"string"},minimum:1,uniqueItems:!0}},additionalProperties:!1}],messages:{pipePrefix:"@Pipes should be prefixed by {{prefixes}}"}},defaultOptions:[{prefixes:[]}],create:(t,[{prefixes:r}])=>({[e.Selectors.PIPE_CLASS_DECORATOR](s){const o=e.ASTUtils.getDecoratorPropertyValue(s,"name");if(!o)return;if(!function(e){return Array.isArray(e)&&e.length>0}(r))return;const a=r.join("|"),n=e.SelectorUtils.SelectorValidator.prefix(a,"camelCase");let i;e.ASTUtils.isStringLiteral(o)?i=o.value:e.ASTUtils.isTemplateLiteral(o)&&o.quasis[0]&&(i=o.quasis[0].value.raw),i&&(n.apply(this,[i])||t.report({node:o,messageId:"pipePrefix",data:{prefixes:e.toHumanReadableText(r)}}))}})});module.exports={configs:{all:{extends:"./configs/base.json",rules:{"@angular-eslint/component-class-suffix":"error","@angular-eslint/component-max-inline-declarations":"error","@angular-eslint/component-selector":"error","@angular-eslint/contextual-decorator":"error","@angular-eslint/contextual-lifecycle":"error","@angular-eslint/directive-class-suffix":"error","@angular-eslint/directive-selector":"error","@angular-eslint/no-attribute-decorator":"error","@angular-eslint/no-conflicting-lifecycle":"error","@angular-eslint/no-empty-lifecycle-method":"error","@angular-eslint/no-forward-ref":"error","@angular-eslint/no-host-metadata-property":"error","@angular-eslint/no-input-prefix":"error","@angular-eslint/no-input-rename":"error","@angular-eslint/no-inputs-metadata-property":"error","@angular-eslint/no-lifecycle-call":"error","@angular-eslint/no-output-native":"error","@angular-eslint/no-output-on-prefix":"error","@angular-eslint/no-output-rename":"error","@angular-eslint/no-outputs-metadata-property":"error","@angular-eslint/no-pipe-impure":"error","@angular-eslint/no-queries-metadata-property":"error","@angular-eslint/pipe-prefix":"error","@angular-eslint/prefer-on-push-component-change-detection":"error","@angular-eslint/prefer-output-readonly":"error","@angular-eslint/relative-url-prefix":"error","@angular-eslint/sort-ngmodule-metadata-arrays":"error","@angular-eslint/use-component-selector":"error","@angular-eslint/use-component-view-encapsulation":"error","@angular-eslint/use-injectable-provided-in":"error","@angular-eslint/use-lifecycle-interface":"error","@angular-eslint/use-pipe-transform-interface":"error"}},base:{parser:"@typescript-eslint/parser",plugins:["@typescript-eslint","@angular-eslint"]},recommended:{extends:"./configs/base.json",rules:{"@angular-eslint/component-class-suffix":"error","@angular-eslint/contextual-lifecycle":"error","@angular-eslint/directive-class-suffix":"error","@angular-eslint/no-empty-lifecycle-method":"error","@angular-eslint/no-host-metadata-property":"error","@angular-eslint/no-input-rename":"error","@angular-eslint/no-inputs-metadata-property":"error","@angular-eslint/no-output-native":"error","@angular-eslint/no-output-on-prefix":"error","@angular-eslint/no-output-rename":"error","@angular-eslint/no-outputs-metadata-property":"error","@angular-eslint/use-lifecycle-interface":"warn","@angular-eslint/use-pipe-transform-interface":"error"}},"recommended--extra":{extends:"./configs/base.json",rules:{"no-restricted-imports":["error",{paths:[{name:"rxjs/Rx",message:"Please import directly from 'rxjs' instead"}]}],"@typescript-eslint/member-ordering":["error",{default:["static-field","instance-field","static-method","instance-method"]}],"no-restricted-syntax":["error",{selector:'CallExpression[callee.object.name="console"][callee.property.name=/^(debug|info|time|timeEnd|trace)$/]',message:"Unexpected property on console object was called"}],"@typescript-eslint/no-inferrable-types":["error",{ignoreParameters:!0}],"@typescript-eslint/no-non-null-assertion":"error","no-fallthrough":"error"}},"ng-cli-compat":{extends:["./configs/base.json"],env:{browser:!0,es6:!0,node:!0},plugins:["eslint-plugin-import","eslint-plugin-jsdoc","eslint-plugin-prefer-arrow"],rules:{"@typescript-eslint/interface-name-prefix":"off","@typescript-eslint/explicit-member-accessibility":"off","sort-keys":"off","@angular-eslint/component-class-suffix":"error","@angular-eslint/component-selector":["error",{type:"element",prefix:"app",style:"kebab-case"}],"@angular-eslint/contextual-lifecycle":"error","@angular-eslint/directive-class-suffix":"error","@angular-eslint/directive-selector":["error",{type:"attribute",prefix:"app",style:"camelCase"}],"@angular-eslint/no-conflicting-lifecycle":"error","@angular-eslint/no-host-metadata-property":"error","@angular-eslint/no-input-rename":"error","@angular-eslint/no-inputs-metadata-property":"error","@angular-eslint/no-output-native":"error","@angular-eslint/no-output-on-prefix":"error","@angular-eslint/no-output-rename":"error","@angular-eslint/no-outputs-metadata-property":"error","@angular-eslint/use-lifecycle-interface":"error","@angular-eslint/use-pipe-transform-interface":"error","@typescript-eslint/adjacent-overload-signatures":"error","@typescript-eslint/array-type":"off","@typescript-eslint/ban-types":["error",{types:{Object:{message:"Avoid using the `Object` type. Did you mean `object`?"},Function:{message:"Avoid using the `Function` type. Prefer a specific function type, like `() => void`."},Boolean:{message:"Avoid using the `Boolean` type. Did you mean `boolean`?"},Number:{message:"Avoid using the `Number` type. Did you mean `number`?"},String:{message:"Avoid using the `String` type. Did you mean `string`?"},Symbol:{message:"Avoid using the `Symbol` type. Did you mean `symbol`?"}}}],"@typescript-eslint/consistent-type-assertions":"error","@typescript-eslint/dot-notation":"error","@typescript-eslint/member-ordering":"error","@typescript-eslint/naming-convention":"error","@typescript-eslint/no-empty-function":"off","@typescript-eslint/no-empty-interface":"error","@typescript-eslint/no-explicit-any":"off","@typescript-eslint/no-inferrable-types":["error",{ignoreParameters:!0}],"@typescript-eslint/no-misused-new":"error","@typescript-eslint/no-namespace":"error","@typescript-eslint/no-non-null-assertion":"error","@typescript-eslint/no-parameter-properties":"off","@typescript-eslint/no-unused-expressions":"error","@typescript-eslint/no-use-before-define":"off","@typescript-eslint/no-var-requires":"off","@typescript-eslint/prefer-for-of":"error","@typescript-eslint/prefer-function-type":"error","@typescript-eslint/prefer-namespace-keyword":"error","@typescript-eslint/triple-slash-reference":["error",{path:"always",types:"prefer-import",lib:"always"}],"@typescript-eslint/unified-signatures":"error",complexity:"off","constructor-super":"error",eqeqeq:["error","smart"],"guard-for-in":"error","id-blacklist":["error","any","Number","number","String","string","Boolean","boolean","Undefined","undefined"],"id-match":"error","import/no-deprecated":"warn","jsdoc/newline-after-description":"error","jsdoc/no-types":"error","max-classes-per-file":"off","no-bitwise":"error","no-caller":"error","no-cond-assign":"error","no-console":["error",{allow:["log","warn","dir","timeLog","assert","clear","count","countReset","group","groupEnd","table","dirxml","error","groupCollapsed","Console","profile","profileEnd","timeStamp","context"]}],"no-debugger":"error","no-empty":"off","no-eval":"error","no-fallthrough":"error","no-invalid-this":"off","no-new-wrappers":"error","no-restricted-imports":["error",{name:"rxjs/Rx",message:"Please import directly from 'rxjs' instead"}],"@typescript-eslint/no-shadow":["error",{hoist:"all"}],"no-throw-literal":"error","no-undef-init":"error","no-underscore-dangle":"error","no-unsafe-finally":"error","no-unused-labels":"error","no-var":"error","object-shorthand":"error","one-var":["error","never"],"prefer-arrow/prefer-arrow-functions":"error","prefer-const":"error",radix:"error","use-isnan":"error","valid-typeof":"off"}},"ng-cli-compat--formatting-add-on":{plugins:["eslint-plugin-jsdoc"],rules:{"arrow-body-style":"error","arrow-parens":"off","comma-dangle":"off",curly:"error","eol-last":"error","jsdoc/check-alignment":"error","max-len":["error",{code:140}],"new-parens":"error","no-multiple-empty-lines":"off","no-trailing-spaces":"error","quote-props":["error","as-needed"],"space-before-function-paren":["error",{anonymous:"never",asyncArrow:"always",named:"never"}],"@typescript-eslint/member-delimiter-style":["error",{multiline:{delimiter:"semi",requireLast:!0},singleline:{delimiter:"semi",requireLast:!1}}],quotes:"off","@typescript-eslint/quotes":["error","single",{allowTemplateLiterals:!0}],"@typescript-eslint/semi":["error","always"],"@typescript-eslint/type-annotation-spacing":"error"}}},rules:{"contextual-decorator":o,"component-class-suffix":n,"component-max-inline-declarations":c,"component-selector":u,"contextual-lifecycle":d,"directive-class-suffix":m,"directive-selector":g,"no-attribute-decorator":f,"no-conflicting-lifecycle":S,"no-forward-ref":T,"no-host-metadata-property":x,"no-input-prefix":E,"no-input-rename":I,"no-inputs-metadata-property":R,"no-lifecycle-call":U,"no-output-native":v,"no-output-on-prefix":P,"no-output-rename":D,"no-outputs-metadata-property":N,"no-pipe-impure":_,"no-queries-metadata-property":L,"no-empty-lifecycle-method":w,[M]:k,"prefer-output-readonly":F,"relative-url-prefix":B,"sort-ngmodule-metadata-arrays":Y,"use-component-selector":W,"use-component-view-encapsulation":H,"use-injectable-provided-in":G,"use-lifecycle-interface":K,"use-pipe-transform-interface":z,"pipe-prefix":Q}};
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
const all_json_1 = __importDefault(require("./configs/all.json"));
|
|
29
|
+
const recommended_json_1 = __importDefault(require("./configs/recommended.json"));
|
|
30
|
+
const component_class_suffix_1 = __importStar(require("./rules/component-class-suffix"));
|
|
31
|
+
const component_max_inline_declarations_1 = __importStar(require("./rules/component-max-inline-declarations"));
|
|
32
|
+
const component_selector_1 = __importStar(require("./rules/component-selector"));
|
|
33
|
+
const contextual_decorator_1 = __importStar(require("./rules/contextual-decorator"));
|
|
34
|
+
const contextual_lifecycle_1 = __importStar(require("./rules/contextual-lifecycle"));
|
|
35
|
+
const directive_class_suffix_1 = __importStar(require("./rules/directive-class-suffix"));
|
|
36
|
+
const directive_selector_1 = __importStar(require("./rules/directive-selector"));
|
|
37
|
+
const no_attribute_decorator_1 = __importStar(require("./rules/no-attribute-decorator"));
|
|
38
|
+
const no_conflicting_lifecycle_1 = __importStar(require("./rules/no-conflicting-lifecycle"));
|
|
39
|
+
const no_empty_lifecycle_method_1 = __importStar(require("./rules/no-empty-lifecycle-method"));
|
|
40
|
+
const no_forward_ref_1 = __importStar(require("./rules/no-forward-ref"));
|
|
41
|
+
const no_host_metadata_property_1 = __importStar(require("./rules/no-host-metadata-property"));
|
|
42
|
+
const no_input_prefix_1 = __importStar(require("./rules/no-input-prefix"));
|
|
43
|
+
const no_input_rename_1 = __importStar(require("./rules/no-input-rename"));
|
|
44
|
+
const no_inputs_metadata_property_1 = __importStar(require("./rules/no-inputs-metadata-property"));
|
|
45
|
+
const no_lifecycle_call_1 = __importStar(require("./rules/no-lifecycle-call"));
|
|
46
|
+
const no_output_native_1 = __importStar(require("./rules/no-output-native"));
|
|
47
|
+
const no_output_on_prefix_1 = __importStar(require("./rules/no-output-on-prefix"));
|
|
48
|
+
const no_output_rename_1 = __importStar(require("./rules/no-output-rename"));
|
|
49
|
+
const no_outputs_metadata_property_1 = __importStar(require("./rules/no-outputs-metadata-property"));
|
|
50
|
+
const no_pipe_impure_1 = __importStar(require("./rules/no-pipe-impure"));
|
|
51
|
+
const no_queries_metadata_property_1 = __importStar(require("./rules/no-queries-metadata-property"));
|
|
52
|
+
const pipe_prefix_1 = __importStar(require("./rules/pipe-prefix"));
|
|
53
|
+
const prefer_on_push_component_change_detection_1 = __importStar(require("./rules/prefer-on-push-component-change-detection"));
|
|
54
|
+
const prefer_output_readonly_1 = __importStar(require("./rules/prefer-output-readonly"));
|
|
55
|
+
const relative_url_prefix_1 = __importStar(require("./rules/relative-url-prefix"));
|
|
56
|
+
const sort_ngmodule_metadata_arrays_1 = __importStar(require("./rules/sort-ngmodule-metadata-arrays"));
|
|
57
|
+
const use_component_selector_1 = __importStar(require("./rules/use-component-selector"));
|
|
58
|
+
const use_component_view_encapsulation_1 = __importStar(require("./rules/use-component-view-encapsulation"));
|
|
59
|
+
const use_injectable_provided_in_1 = __importStar(require("./rules/use-injectable-provided-in"));
|
|
60
|
+
const use_lifecycle_interface_1 = __importStar(require("./rules/use-lifecycle-interface"));
|
|
61
|
+
const use_pipe_transform_interface_1 = __importStar(require("./rules/use-pipe-transform-interface"));
|
|
62
|
+
module.exports = {
|
|
63
|
+
configs: {
|
|
64
|
+
all: all_json_1.default,
|
|
65
|
+
recommended: recommended_json_1.default,
|
|
66
|
+
},
|
|
67
|
+
rules: {
|
|
68
|
+
[component_class_suffix_1.RULE_NAME]: component_class_suffix_1.default,
|
|
69
|
+
[component_max_inline_declarations_1.RULE_NAME]: component_max_inline_declarations_1.default,
|
|
70
|
+
[component_selector_1.RULE_NAME]: component_selector_1.default,
|
|
71
|
+
[contextual_decorator_1.RULE_NAME]: contextual_decorator_1.default,
|
|
72
|
+
[contextual_lifecycle_1.RULE_NAME]: contextual_lifecycle_1.default,
|
|
73
|
+
[directive_class_suffix_1.RULE_NAME]: directive_class_suffix_1.default,
|
|
74
|
+
[directive_selector_1.RULE_NAME]: directive_selector_1.default,
|
|
75
|
+
[no_attribute_decorator_1.RULE_NAME]: no_attribute_decorator_1.default,
|
|
76
|
+
[no_conflicting_lifecycle_1.RULE_NAME]: no_conflicting_lifecycle_1.default,
|
|
77
|
+
[no_empty_lifecycle_method_1.RULE_NAME]: no_empty_lifecycle_method_1.default,
|
|
78
|
+
[no_forward_ref_1.RULE_NAME]: no_forward_ref_1.default,
|
|
79
|
+
[no_host_metadata_property_1.RULE_NAME]: no_host_metadata_property_1.default,
|
|
80
|
+
[no_input_prefix_1.RULE_NAME]: no_input_prefix_1.default,
|
|
81
|
+
[no_input_rename_1.RULE_NAME]: no_input_rename_1.default,
|
|
82
|
+
[no_inputs_metadata_property_1.RULE_NAME]: no_inputs_metadata_property_1.default,
|
|
83
|
+
[no_lifecycle_call_1.RULE_NAME]: no_lifecycle_call_1.default,
|
|
84
|
+
[no_output_native_1.RULE_NAME]: no_output_native_1.default,
|
|
85
|
+
[no_output_on_prefix_1.RULE_NAME]: no_output_on_prefix_1.default,
|
|
86
|
+
[no_output_rename_1.RULE_NAME]: no_output_rename_1.default,
|
|
87
|
+
[no_outputs_metadata_property_1.RULE_NAME]: no_outputs_metadata_property_1.default,
|
|
88
|
+
[no_pipe_impure_1.RULE_NAME]: no_pipe_impure_1.default,
|
|
89
|
+
[no_queries_metadata_property_1.RULE_NAME]: no_queries_metadata_property_1.default,
|
|
90
|
+
[pipe_prefix_1.RULE_NAME]: pipe_prefix_1.default,
|
|
91
|
+
[prefer_on_push_component_change_detection_1.RULE_NAME]: prefer_on_push_component_change_detection_1.default,
|
|
92
|
+
[prefer_output_readonly_1.RULE_NAME]: prefer_output_readonly_1.default,
|
|
93
|
+
[relative_url_prefix_1.RULE_NAME]: relative_url_prefix_1.default,
|
|
94
|
+
[sort_ngmodule_metadata_arrays_1.RULE_NAME]: sort_ngmodule_metadata_arrays_1.default,
|
|
95
|
+
[use_component_selector_1.RULE_NAME]: use_component_selector_1.default,
|
|
96
|
+
[use_component_view_encapsulation_1.RULE_NAME]: use_component_view_encapsulation_1.default,
|
|
97
|
+
[use_injectable_provided_in_1.RULE_NAME]: use_injectable_provided_in_1.default,
|
|
98
|
+
[use_lifecycle_interface_1.RULE_NAME]: use_lifecycle_interface_1.default,
|
|
99
|
+
[use_pipe_transform_interface_1.RULE_NAME]: use_pipe_transform_interface_1.default,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'component-class-suffix';
|
|
7
|
+
const STYLE_GUIDE_LINK = 'https://angular.io/styleguide#style-02-03';
|
|
8
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
9
|
+
name: exports.RULE_NAME,
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
description: `Classes decorated with @Component must have suffix "Component" (or custom) in their name. See more at ${STYLE_GUIDE_LINK}`,
|
|
14
|
+
recommended: 'error',
|
|
15
|
+
},
|
|
16
|
+
schema: [
|
|
17
|
+
{
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
suffixes: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
items: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
messages: {
|
|
31
|
+
componentClassSuffix: `Component class names should end with one of these suffixes: {{suffixes}} (${STYLE_GUIDE_LINK})`,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultOptions: [
|
|
35
|
+
{
|
|
36
|
+
suffixes: ['Component'],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
create(context, [{ suffixes }]) {
|
|
40
|
+
return {
|
|
41
|
+
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
42
|
+
const classParent = node.parent;
|
|
43
|
+
const className = utils_1.ASTUtils.getClassName(classParent);
|
|
44
|
+
if (!className ||
|
|
45
|
+
!suffixes.some((suffix) => className.endsWith(suffix))) {
|
|
46
|
+
context.report({
|
|
47
|
+
node: classParent.id ? classParent.id : classParent,
|
|
48
|
+
messageId: 'componentClassSuffix',
|
|
49
|
+
data: { suffixes: (0, utils_1.toHumanReadableText)(suffixes) },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'component-max-inline-declarations';
|
|
7
|
+
const STYLE_GUIDE_LINK = 'https://angular.io/guide/styleguide#style-05-04';
|
|
8
|
+
const NEW_LINE_REGEXP = /\r\n|\r|\n/;
|
|
9
|
+
const DEFAULT_TEMPLATE_LIMIT = 3;
|
|
10
|
+
const DEFAULT_STYLES_LIMIT = 3;
|
|
11
|
+
const DEFAULT_ANIMATIONS_LIMIT = 15;
|
|
12
|
+
function getLinesCount(node) {
|
|
13
|
+
if (utils_1.ASTUtils.isTemplateLiteral(node)) {
|
|
14
|
+
return node.quasis[0].value.raw.trim().split(NEW_LINE_REGEXP).length;
|
|
15
|
+
}
|
|
16
|
+
if (utils_1.ASTUtils.isLiteral(node)) {
|
|
17
|
+
return node.raw.trim().split(NEW_LINE_REGEXP).length;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
22
|
+
name: exports.RULE_NAME,
|
|
23
|
+
meta: {
|
|
24
|
+
type: 'suggestion',
|
|
25
|
+
docs: {
|
|
26
|
+
description: `Enforces a maximum number of lines in inline template, styles and animations. See more at ${STYLE_GUIDE_LINK}`,
|
|
27
|
+
recommended: false,
|
|
28
|
+
},
|
|
29
|
+
schema: [
|
|
30
|
+
{
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
template: { minimum: 0, type: 'number' },
|
|
34
|
+
styles: { minimum: 0, type: 'number' },
|
|
35
|
+
animations: { minimum: 0, type: 'number' },
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
messages: {
|
|
41
|
+
componentMaxInlineDeclarations: `\`{{propertyType}}\` has too many lines ({{lineCount}}). Maximum allowed is {{max}} (${STYLE_GUIDE_LINK})`,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
defaultOptions: [
|
|
45
|
+
{
|
|
46
|
+
template: DEFAULT_TEMPLATE_LIMIT,
|
|
47
|
+
styles: DEFAULT_STYLES_LIMIT,
|
|
48
|
+
animations: DEFAULT_ANIMATIONS_LIMIT,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
create(context, [{ template = DEFAULT_TEMPLATE_LIMIT, styles = DEFAULT_STYLES_LIMIT, animations = DEFAULT_ANIMATIONS_LIMIT, },]) {
|
|
52
|
+
return {
|
|
53
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='template']`]({ value, }) {
|
|
54
|
+
const lineCount = getLinesCount(value);
|
|
55
|
+
if (lineCount <= template)
|
|
56
|
+
return;
|
|
57
|
+
context.report({
|
|
58
|
+
node: value,
|
|
59
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
60
|
+
data: {
|
|
61
|
+
lineCount,
|
|
62
|
+
max: template,
|
|
63
|
+
propertyType: 'template',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styles']`]({ value, }) {
|
|
68
|
+
if (!utils_1.ASTUtils.isArrayExpression(value))
|
|
69
|
+
return;
|
|
70
|
+
const lineCount = value.elements.reduce((lines, element) => lines + (element ? getLinesCount(element) : 0), 0);
|
|
71
|
+
if (lineCount <= styles)
|
|
72
|
+
return;
|
|
73
|
+
context.report({
|
|
74
|
+
node: value,
|
|
75
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
76
|
+
data: {
|
|
77
|
+
lineCount,
|
|
78
|
+
max: styles,
|
|
79
|
+
propertyType: 'styles',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
[`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='animations']`]({ value, }) {
|
|
84
|
+
if (!utils_1.ASTUtils.isArrayExpression(value) || value.elements.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
const animationsBracketsSize = 2;
|
|
87
|
+
const lineCount = Math.max(value.loc.end.line - value.loc.start.line - animationsBracketsSize, 1);
|
|
88
|
+
if (lineCount <= animations)
|
|
89
|
+
return;
|
|
90
|
+
context.report({
|
|
91
|
+
node: value,
|
|
92
|
+
messageId: 'componentMaxInlineDeclarations',
|
|
93
|
+
data: {
|
|
94
|
+
lineCount,
|
|
95
|
+
max: animations,
|
|
96
|
+
propertyType: 'animations',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const utils_2 = require("@typescript-eslint/utils");
|
|
6
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
7
|
+
const VIEW_ENCAPSULATION_SHADOW_DOM = 'ShadowDom';
|
|
8
|
+
const VIEW_ENCAPSULATION = 'ViewEncapsulation';
|
|
9
|
+
exports.RULE_NAME = 'component-selector';
|
|
10
|
+
const STYLE_GUIDE_PREFIX_LINK = 'https://angular.io/guide/styleguide#style-02-07';
|
|
11
|
+
const STYLE_GUIDE_STYLE_LINK = 'https://angular.io/guide/styleguide#style-05-02';
|
|
12
|
+
const STYLE_GUIDE_TYPE_LINK = 'https://angular.io/guide/styleguide#style-05-03';
|
|
13
|
+
const SHADOW_DOM_ENCAPSULATED_STYLE_LINK = 'https://github.com/angular-eslint/angular-eslint/issues/534';
|
|
14
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
15
|
+
name: exports.RULE_NAME,
|
|
16
|
+
meta: {
|
|
17
|
+
type: 'suggestion',
|
|
18
|
+
docs: {
|
|
19
|
+
description: `Component selectors should follow given naming rules. See more at ${STYLE_GUIDE_PREFIX_LINK}, ${STYLE_GUIDE_STYLE_LINK}
|
|
20
|
+
and ${STYLE_GUIDE_TYPE_LINK}.`,
|
|
21
|
+
recommended: false,
|
|
22
|
+
},
|
|
23
|
+
schema: [
|
|
24
|
+
{
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
type: {
|
|
28
|
+
oneOf: [
|
|
29
|
+
{ type: 'string' },
|
|
30
|
+
{
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
enum: [
|
|
34
|
+
utils_1.SelectorUtils.OPTION_TYPE_ELEMENT,
|
|
35
|
+
utils_1.SelectorUtils.OPTION_TYPE_ATTRIBUTE,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
prefix: {
|
|
42
|
+
oneOf: [{ type: 'string' }, { type: 'array' }],
|
|
43
|
+
},
|
|
44
|
+
style: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: [
|
|
47
|
+
utils_1.ASTUtils.OPTION_STYLE_CAMEL_CASE,
|
|
48
|
+
utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE,
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
messages: {
|
|
56
|
+
prefixFailure: `The selector should start with one of these prefixes: {{prefix}} (${STYLE_GUIDE_PREFIX_LINK})`,
|
|
57
|
+
styleFailure: `The selector should be {{style}} (${STYLE_GUIDE_STYLE_LINK})`,
|
|
58
|
+
styleAndPrefixFailure: `The selector should be {{style}} and start with one of these prefixes: {{prefix}} (${STYLE_GUIDE_STYLE_LINK} and ${STYLE_GUIDE_PREFIX_LINK})`,
|
|
59
|
+
typeFailure: `The selector should be used as an {{type}} (${STYLE_GUIDE_TYPE_LINK})`,
|
|
60
|
+
shadowDomEncapsulatedStyleFailure: `The selector of a ShadowDom-encapsulated component should be \`${utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE}\` (${SHADOW_DOM_ENCAPSULATED_STYLE_LINK})`,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
defaultOptions: [
|
|
64
|
+
{
|
|
65
|
+
type: '',
|
|
66
|
+
prefix: '',
|
|
67
|
+
style: '',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
create(context, [{ type, prefix, style }]) {
|
|
71
|
+
return {
|
|
72
|
+
[utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
|
|
73
|
+
const rawSelectors = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'selector');
|
|
74
|
+
if (!rawSelectors) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const isValidOptions = utils_1.SelectorUtils.checkValidOptions(type, prefix, style);
|
|
78
|
+
if (!isValidOptions) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// override `style` for ShadowDom-encapsulated components. See https://github.com/angular-eslint/angular-eslint/issues/534.
|
|
82
|
+
const overrideStyle = style !== utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE &&
|
|
83
|
+
hasEncapsulationShadowDomProperty(node)
|
|
84
|
+
? utils_1.ASTUtils.OPTION_STYLE_KEBAB_CASE
|
|
85
|
+
: style;
|
|
86
|
+
const hasExpectedSelector = utils_1.SelectorUtils.checkSelector(rawSelectors, type, (0, utils_1.arrayify)(prefix), overrideStyle);
|
|
87
|
+
if (hasExpectedSelector === null) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!hasExpectedSelector.hasExpectedType) {
|
|
91
|
+
utils_1.SelectorUtils.reportTypeError(rawSelectors, type, context);
|
|
92
|
+
}
|
|
93
|
+
else if (!hasExpectedSelector.hasExpectedStyle) {
|
|
94
|
+
if (style === overrideStyle) {
|
|
95
|
+
if (!hasExpectedSelector.hasExpectedPrefix) {
|
|
96
|
+
utils_1.SelectorUtils.reportStyleAndPrefixError(rawSelectors, style, prefix, context);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
utils_1.SelectorUtils.reportStyleError(rawSelectors, style, context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
context.report({
|
|
104
|
+
node: rawSelectors,
|
|
105
|
+
messageId: 'shadowDomEncapsulatedStyleFailure',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (!hasExpectedSelector.hasExpectedPrefix) {
|
|
110
|
+
utils_1.SelectorUtils.reportPrefixError(rawSelectors, prefix, context);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
function hasEncapsulationShadowDomProperty(node) {
|
|
117
|
+
const encapsulationValue = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'encapsulation');
|
|
118
|
+
return (encapsulationValue &&
|
|
119
|
+
utils_1.ASTUtils.isMemberExpression(encapsulationValue) &&
|
|
120
|
+
utils_2.ASTUtils.isIdentifier(encapsulationValue.object) &&
|
|
121
|
+
encapsulationValue.object.name === VIEW_ENCAPSULATION &&
|
|
122
|
+
utils_2.ASTUtils.isIdentifier(encapsulationValue.property) &&
|
|
123
|
+
encapsulationValue.property.name === VIEW_ENCAPSULATION_SHADOW_DOM);
|
|
124
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_NAME = void 0;
|
|
4
|
+
const utils_1 = require("@angular-eslint/utils");
|
|
5
|
+
const create_eslint_rule_1 = require("../utils/create-eslint-rule");
|
|
6
|
+
exports.RULE_NAME = 'contextual-decorator';
|
|
7
|
+
exports.default = (0, create_eslint_rule_1.createESLintRule)({
|
|
8
|
+
name: exports.RULE_NAME,
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Ensures that classes use contextual decorators in its body',
|
|
13
|
+
recommended: false,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
contextualDecorator: 'Decorator out of context for "@{{classDecoratorName}}()"',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
'MethodDefinition[kind=/^(get|set|method)$/], PropertyDefinition, TSParameterProperty'(node) {
|
|
24
|
+
validateNode(context, node);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
function validateNode(context, node) {
|
|
30
|
+
var _a;
|
|
31
|
+
if (!((_a = node.decorators) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const classDeclaration = utils_1.ASTUtils.getNearestNodeFrom(node, utils_1.ASTUtils.isClassDeclaration);
|
|
35
|
+
if (!classDeclaration) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const classDecoratorName = utils_1.ASTUtils.getAngularClassDecorator(classDeclaration);
|
|
39
|
+
if (!classDecoratorName) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const decorator of node.decorators) {
|
|
43
|
+
validateDecorator(context, decorator, classDecoratorName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function validateDecorator(context, decorator, classDecoratorName) {
|
|
47
|
+
const decoratorName = utils_1.ASTUtils.getDecoratorName(decorator);
|
|
48
|
+
if (!decoratorName || !utils_1.ASTUtils.isAngularInnerClassDecorator(decoratorName)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const allowedDecorators = utils_1.ASTUtils.ANGULAR_CLASS_DECORATOR_MAPPER.get(classDecoratorName);
|
|
52
|
+
if (allowedDecorators === null || allowedDecorators === void 0 ? void 0 : allowedDecorators.has(decoratorName)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
context.report({
|
|
56
|
+
node: decorator,
|
|
57
|
+
messageId: 'contextualDecorator',
|
|
58
|
+
data: { classDecoratorName },
|
|
59
|
+
});
|
|
60
|
+
}
|