@angular-eslint/eslint-plugin-template 19.3.1-alpha.9 → 19.4.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/README.md CHANGED
@@ -64,6 +64,7 @@ Please see https://github.com/angular-eslint/angular-eslint for full usage instr
64
64
  | [`prefer-contextual-for-variables`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-contextual-for-variables.md) | Ensures that contextual variables are used in @for blocks where possible instead of aliasing them. | | :wrench: | | |
65
65
  | [`prefer-control-flow`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-control-flow.md) | Ensures that the built-in control flow is used. | | | | |
66
66
  | [`prefer-ngsrc`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-ngsrc.md) | Ensures ngSrc is used instead of src for img elements | | | | |
67
+ | [`prefer-template-literal`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-template-literal.md) | Ensure that template literals are used instead of concatenating strings or expressions. | | :wrench: | | |
67
68
  | [`role-has-required-aria`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/role-has-required-aria.md) | [Accessibility] Ensures elements with ARIA roles have all required properties for that role. | | | :bulb: | :accessibility: |
68
69
  | [`table-scope`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/table-scope.md) | [Accessibility] Ensures that the `scope` attribute is only used on the `<th>` element | | :wrench: | | :accessibility: |
69
70
  | [`use-track-by-function`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/use-track-by-function.md) | Ensures trackBy function is used | | | | |
@@ -1,17 +1,17 @@
1
1
  {
2
- "parser": "@angular-eslint/template-parser",
3
- "plugins": ["@angular-eslint/template"],
4
- "rules": {
5
- "@angular-eslint/template/alt-text": "error",
6
- "@angular-eslint/template/click-events-have-key-events": "error",
7
- "@angular-eslint/template/elements-content": "error",
8
- "@angular-eslint/template/interactive-supports-focus": "error",
9
- "@angular-eslint/template/label-has-associated-control": "error",
10
- "@angular-eslint/template/mouse-events-have-key-events": "error",
11
- "@angular-eslint/template/no-autofocus": "error",
12
- "@angular-eslint/template/no-distracting-elements": "error",
13
- "@angular-eslint/template/role-has-required-aria": "error",
14
- "@angular-eslint/template/table-scope": "error",
15
- "@angular-eslint/template/valid-aria": "error"
16
- }
2
+ "parser": "@angular-eslint/template-parser",
3
+ "plugins": ["@angular-eslint/template"],
4
+ "rules": {
5
+ "@angular-eslint/template/alt-text": "error",
6
+ "@angular-eslint/template/click-events-have-key-events": "error",
7
+ "@angular-eslint/template/elements-content": "error",
8
+ "@angular-eslint/template/interactive-supports-focus": "error",
9
+ "@angular-eslint/template/label-has-associated-control": "error",
10
+ "@angular-eslint/template/mouse-events-have-key-events": "error",
11
+ "@angular-eslint/template/no-autofocus": "error",
12
+ "@angular-eslint/template/no-distracting-elements": "error",
13
+ "@angular-eslint/template/role-has-required-aria": "error",
14
+ "@angular-eslint/template/table-scope": "error",
15
+ "@angular-eslint/template/valid-aria": "error"
16
+ }
17
17
  }
@@ -1,37 +1,38 @@
1
1
  {
2
- "parser": "@angular-eslint/template-parser",
3
- "plugins": ["@angular-eslint/template"],
4
- "rules": {
5
- "@angular-eslint/template/alt-text": "error",
6
- "@angular-eslint/template/attributes-order": "error",
7
- "@angular-eslint/template/banana-in-box": "error",
8
- "@angular-eslint/template/button-has-type": "error",
9
- "@angular-eslint/template/click-events-have-key-events": "error",
10
- "@angular-eslint/template/conditional-complexity": "error",
11
- "@angular-eslint/template/cyclomatic-complexity": "error",
12
- "@angular-eslint/template/elements-content": "error",
13
- "@angular-eslint/template/eqeqeq": "error",
14
- "@angular-eslint/template/i18n": "error",
15
- "@angular-eslint/template/interactive-supports-focus": "error",
16
- "@angular-eslint/template/label-has-associated-control": "error",
17
- "@angular-eslint/template/mouse-events-have-key-events": "error",
18
- "@angular-eslint/template/no-any": "error",
19
- "@angular-eslint/template/no-autofocus": "error",
20
- "@angular-eslint/template/no-call-expression": "error",
21
- "@angular-eslint/template/no-distracting-elements": "error",
22
- "@angular-eslint/template/no-duplicate-attributes": "error",
23
- "@angular-eslint/template/no-inline-styles": "error",
24
- "@angular-eslint/template/no-interpolation-in-attributes": "error",
25
- "@angular-eslint/template/no-negated-async": "error",
26
- "@angular-eslint/template/no-positive-tabindex": "error",
27
- "@angular-eslint/template/prefer-contextual-for-variables": "error",
28
- "@angular-eslint/template/prefer-control-flow": "error",
29
- "@angular-eslint/template/prefer-ngsrc": "error",
30
- "@angular-eslint/template/prefer-self-closing-tags": "error",
31
- "@angular-eslint/template/prefer-static-string-properties": "error",
32
- "@angular-eslint/template/role-has-required-aria": "error",
33
- "@angular-eslint/template/table-scope": "error",
34
- "@angular-eslint/template/use-track-by-function": "error",
35
- "@angular-eslint/template/valid-aria": "error"
36
- }
2
+ "parser": "@angular-eslint/template-parser",
3
+ "plugins": ["@angular-eslint/template"],
4
+ "rules": {
5
+ "@angular-eslint/template/alt-text": "error",
6
+ "@angular-eslint/template/attributes-order": "error",
7
+ "@angular-eslint/template/banana-in-box": "error",
8
+ "@angular-eslint/template/button-has-type": "error",
9
+ "@angular-eslint/template/click-events-have-key-events": "error",
10
+ "@angular-eslint/template/conditional-complexity": "error",
11
+ "@angular-eslint/template/cyclomatic-complexity": "error",
12
+ "@angular-eslint/template/elements-content": "error",
13
+ "@angular-eslint/template/eqeqeq": "error",
14
+ "@angular-eslint/template/i18n": "error",
15
+ "@angular-eslint/template/interactive-supports-focus": "error",
16
+ "@angular-eslint/template/label-has-associated-control": "error",
17
+ "@angular-eslint/template/mouse-events-have-key-events": "error",
18
+ "@angular-eslint/template/no-any": "error",
19
+ "@angular-eslint/template/no-autofocus": "error",
20
+ "@angular-eslint/template/no-call-expression": "error",
21
+ "@angular-eslint/template/no-distracting-elements": "error",
22
+ "@angular-eslint/template/no-duplicate-attributes": "error",
23
+ "@angular-eslint/template/no-inline-styles": "error",
24
+ "@angular-eslint/template/no-interpolation-in-attributes": "error",
25
+ "@angular-eslint/template/no-negated-async": "error",
26
+ "@angular-eslint/template/no-positive-tabindex": "error",
27
+ "@angular-eslint/template/prefer-contextual-for-variables": "error",
28
+ "@angular-eslint/template/prefer-control-flow": "error",
29
+ "@angular-eslint/template/prefer-ngsrc": "error",
30
+ "@angular-eslint/template/prefer-self-closing-tags": "error",
31
+ "@angular-eslint/template/prefer-static-string-properties": "error",
32
+ "@angular-eslint/template/prefer-template-literal": "error",
33
+ "@angular-eslint/template/role-has-required-aria": "error",
34
+ "@angular-eslint/template/table-scope": "error",
35
+ "@angular-eslint/template/use-track-by-function": "error",
36
+ "@angular-eslint/template/valid-aria": "error"
37
+ }
37
38
  }
@@ -1,9 +1,9 @@
1
1
  {
2
- "parser": "@typescript-eslint/parser",
3
- "parserOptions": {
4
- "ecmaVersion": 2020,
5
- "sourceType": "module"
6
- },
7
- "plugins": ["@angular-eslint/template"],
8
- "processor": "@angular-eslint/template/extract-inline-html"
2
+ "parser": "@typescript-eslint/parser",
3
+ "parserOptions": {
4
+ "ecmaVersion": 2020,
5
+ "sourceType": "module"
6
+ },
7
+ "plugins": ["@angular-eslint/template"],
8
+ "processor": "@angular-eslint/template/extract-inline-html"
9
9
  }
@@ -1,9 +1,9 @@
1
1
  {
2
- "parser": "@angular-eslint/template-parser",
3
- "plugins": ["@angular-eslint/template"],
4
- "rules": {
5
- "@angular-eslint/template/banana-in-box": "error",
6
- "@angular-eslint/template/eqeqeq": "error",
7
- "@angular-eslint/template/no-negated-async": "error"
8
- }
2
+ "parser": "@angular-eslint/template-parser",
3
+ "plugins": ["@angular-eslint/template"],
4
+ "rules": {
5
+ "@angular-eslint/template/banana-in-box": "error",
6
+ "@angular-eslint/template/eqeqeq": "error",
7
+ "@angular-eslint/template/no-negated-async": "error"
8
+ }
9
9
  }
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ declare const _default: {
31
31
  "@angular-eslint/template/prefer-ngsrc": string;
32
32
  "@angular-eslint/template/prefer-self-closing-tags": string;
33
33
  "@angular-eslint/template/prefer-static-string-properties": string;
34
+ "@angular-eslint/template/prefer-template-literal": string;
34
35
  "@angular-eslint/template/role-has-required-aria": string;
35
36
  "@angular-eslint/template/table-scope": string;
36
37
  "@angular-eslint/template/use-track-by-function": string;
@@ -103,7 +104,7 @@ declare const _default: {
103
104
  "no-distracting-elements": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noDistractingElements", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
104
105
  "no-duplicate-attributes": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-duplicate-attributes").MessageIds, import("./rules/no-duplicate-attributes").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
105
106
  "no-inline-styles": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInlineStyles", import("./rules/no-inline-styles").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
106
- "no-interpolation-in-attributes": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInterpolationInAttributes", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
107
+ "no-interpolation-in-attributes": import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInterpolationInAttributes", import("./rules/no-interpolation-in-attributes").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
107
108
  "no-negated-async": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-negated-async").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
108
109
  "no-positive-tabindex": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-positive-tabindex").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
109
110
  "prefer-contextual-for-variables": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-contextual-for-variables").MessageIds, import("./rules/prefer-contextual-for-variables").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -111,6 +112,7 @@ declare const _default: {
111
112
  "prefer-self-closing-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSelfClosingTags", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
112
113
  "prefer-static-string-properties": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
113
114
  "prefer-ngsrc": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-ngsrc").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
115
+ "prefer-template-literal": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferTemplateLiteral", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
114
116
  "role-has-required-aria": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/role-has-required-aria").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
115
117
  "table-scope": import("@typescript-eslint/utils/ts-eslint").RuleModule<"tableScope", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
116
118
  "use-track-by-function": import("@typescript-eslint/utils/ts-eslint").RuleModule<"useTrackByFunction", import("./rules/use-track-by-function").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,kBAyCE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,kBA0CE"}
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ const prefer_contextual_for_variables_1 = __importStar(require("./rules/prefer-c
67
67
  const prefer_control_flow_1 = __importStar(require("./rules/prefer-control-flow"));
68
68
  const prefer_self_closing_tags_1 = __importStar(require("./rules/prefer-self-closing-tags"));
69
69
  const prefer_static_string_properties_1 = __importStar(require("./rules/prefer-static-string-properties"));
70
+ const prefer_template_literal_1 = __importStar(require("./rules/prefer-template-literal"));
70
71
  const role_has_required_aria_1 = __importStar(require("./rules/role-has-required-aria"));
71
72
  const table_scope_1 = __importStar(require("./rules/table-scope"));
72
73
  const use_track_by_function_1 = __importStar(require("./rules/use-track-by-function"));
@@ -107,6 +108,7 @@ module.exports = {
107
108
  [prefer_self_closing_tags_1.RULE_NAME]: prefer_self_closing_tags_1.default,
108
109
  [prefer_static_string_properties_1.RULE_NAME]: prefer_static_string_properties_1.default,
109
110
  [prefer_ngsrc_1.RULE_NAME]: prefer_ngsrc_1.default,
111
+ [prefer_template_literal_1.RULE_NAME]: prefer_template_literal_1.default,
110
112
  [role_has_required_aria_1.RULE_NAME]: role_has_required_aria_1.default,
111
113
  [table_scope_1.RULE_NAME]: table_scope_1.default,
112
114
  [use_track_by_function_1.RULE_NAME]: use_track_by_function_1.default,
@@ -1 +1 @@
1
- {"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CA2BT;AAED,KAAK,gBAAgB,GAAG,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAAC;AAExE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAmJlB;AAyDD,wBAAgB,wBAAwB,CACtC,wBAAwB,EAAE;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,EAAE,EAAE,EACL,QAAQ,EAAE,MAAM,GACf,SAAS,OAAO,EAAE,CAgEpB;;;;;;;;;;;AAED,wBASE"}
1
+ {"version":3,"file":"processors.d.ts","sourceRoot":"","sources":["../src/processors.ts"],"names":[],"mappings":"AAKA;;;;;;;GAOG;AACH,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CA2BT;AAED,KAAK,gBAAgB,GAAG,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAAC;AAExE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,gBAAgB,CAqJlB;AAyDD,wBAAgB,wBAAwB,CACtC,wBAAwB,EAAE;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,EAAE,EAAE,EACL,QAAQ,EAAE,MAAM,GACf,SAAS,OAAO,EAAE,CAmEpB;;;;;;;;;;;AAED,wBASE"}
@@ -110,7 +110,9 @@ function preprocessComponentFile(text, filename) {
110
110
  templateText = templatePropertyInitializer.rawText;
111
111
  }
112
112
  if (typescript_1.default.isTemplateExpression(templatePropertyInitializer)) {
113
- templateText = templatePropertyInitializer.getText();
113
+ // The text includes the opening and closing
114
+ // backtick, so trim the first and last characters.
115
+ templateText = templatePropertyInitializer.getText().slice(1, -1);
114
116
  }
115
117
  if (typescript_1.default.isStringLiteral(templatePropertyInitializer)) {
116
118
  templateText = templatePropertyInitializer.text;
@@ -238,6 +240,9 @@ function postprocessComponentFile(multiDimensionalMessages, filename) {
238
240
  message.line += rangeData.lineAndCharacter.start.line;
239
241
  message.endLine += rangeData.lineAndCharacter.start.line;
240
242
  if (message.fix) {
243
+ // The range defines the range of the value that initializes
244
+ // the `template` property, which includes the opening and
245
+ // closing quotes. Add one to move past the opening quote.
241
246
  const startOffset = rangeData.range[0] + 1;
242
247
  message.fix.range = [
243
248
  startOffset + message.fix.range[0],
@@ -1 +1 @@
1
- {"version":3,"file":"eqeqeq.d.ts","sourceRoot":"","sources":["../../src/rules/eqeqeq.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAIzD,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,uBAAuB,CAAC;AAC5D,eAAO,MAAM,SAAS,WAAW,CAAC;;AAGlC,wBA4EG"}
1
+ {"version":3,"file":"eqeqeq.d.ts","sourceRoot":"","sources":["../../src/rules/eqeqeq.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAQzD,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,uBAAuB,CAAC;AAC5D,eAAO,MAAM,SAAS,WAAW,CAAC;;AAGlC,wBA4EG"}
@@ -5,6 +5,7 @@ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-comp
5
5
  const utils_1 = require("@angular-eslint/utils");
6
6
  const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
7
  const get_nearest_node_from_1 = require("../utils/get-nearest-node-from");
8
+ const literal_primitive_1 = require("../utils/literal-primitive");
8
9
  exports.RULE_NAME = 'eqeqeq';
9
10
  const DEFAULT_OPTIONS = { allowNullOrUndefined: false };
10
11
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
@@ -97,9 +98,6 @@ function isLeadingTriviaChar(char) {
97
98
  function isASTWithSource(node) {
98
99
  return node instanceof bundled_angular_compiler_1.ASTWithSource;
99
100
  }
100
- function isLiteralPrimitive(node) {
101
- return node instanceof bundled_angular_compiler_1.LiteralPrimitive;
102
- }
103
101
  function isInterpolation(node) {
104
102
  return node instanceof bundled_angular_compiler_1.Interpolation;
105
103
  }
@@ -107,12 +105,9 @@ function isNumeric(value) {
107
105
  return (!Number.isNaN(Number.parseFloat(String(value))) &&
108
106
  Number.isFinite(Number(value)));
109
107
  }
110
- function isString(value) {
111
- return typeof value === 'string';
112
- }
113
108
  function isStringNonNumericValue(ast) {
114
- return (isLiteralPrimitive(ast) && isString(ast.value) && !isNumeric(ast.value));
109
+ return (0, literal_primitive_1.isStringLiteralPrimitive)(ast) && !isNumeric(ast.value);
115
110
  }
116
111
  function isNilValue(ast) {
117
- return (isLiteralPrimitive(ast) && (ast.value === null || ast.value === undefined));
112
+ return ((0, literal_primitive_1.isLiteralPrimitive)(ast) && (ast.value === null || ast.value === undefined));
118
113
  }
@@ -1,6 +1,8 @@
1
- export type Options = [];
1
+ export type Options = [{
2
+ allowSubstringInterpolation: boolean;
3
+ }];
2
4
  export type MessageIds = 'noInterpolationInAttributes';
3
5
  export declare const RULE_NAME = "no-interpolation-in-attributes";
4
- declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInterpolationInAttributes", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"noInterpolationInAttributes", Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
7
  export default _default;
6
8
  //# sourceMappingURL=no-interpolation-in-attributes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"no-interpolation-in-attributes.d.ts","sourceRoot":"","sources":["../../src/rules/no-interpolation-in-attributes.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,6BAA6B,CAAC;AACvD,eAAO,MAAM,SAAS,mCAAmC,CAAC;;AAE1D,wBAkCG"}
1
+ {"version":3,"file":"no-interpolation-in-attributes.d.ts","sourceRoot":"","sources":["../../src/rules/no-interpolation-in-attributes.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,OAAO,GAAG,CAAC;IAAE,2BAA2B,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AACjE,MAAM,MAAM,UAAU,GAAG,6BAA6B,CAAC;AACvD,eAAO,MAAM,SAAS,mCAAmC,CAAC;;AAY1D,wBAmDG"}
@@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RULE_NAME = void 0;
4
4
  const create_eslint_rule_1 = require("../utils/create-eslint-rule");
5
5
  exports.RULE_NAME = 'no-interpolation-in-attributes';
6
+ const allowSubstringInterpolationDescription = `\
7
+ When \`true\`, only attribute values that are entirely interpolations will fail, whereas values with interpolations that form part of larger strings will be allowed.
8
+
9
+ For example, when set to \`true\` the following code will not fail for the \`alt\` attribute but will still fail for the \`src\` attribute:
10
+
11
+ \`\`\`html
12
+ <img alt="Poke user {{ username }}" src="{{ pokeSrc }}" />
13
+ \`\`\`
14
+ `;
6
15
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
7
16
  name: exports.RULE_NAME,
8
17
  meta: {
@@ -10,16 +19,31 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
10
19
  docs: {
11
20
  description: 'Ensures that property-binding is used instead of interpolation in attributes.',
12
21
  },
13
- schema: [],
22
+ schema: [
23
+ {
24
+ type: 'object',
25
+ properties: {
26
+ allowSubstringInterpolation: {
27
+ type: 'boolean',
28
+ description: allowSubstringInterpolationDescription,
29
+ },
30
+ },
31
+ additionalProperties: false,
32
+ },
33
+ ],
14
34
  messages: {
15
35
  noInterpolationInAttributes: 'Use property binding [attribute]="value" instead of interpolation {{ value }} for an attribute.',
16
36
  },
17
37
  },
18
- defaultOptions: [],
19
- create(context) {
38
+ defaultOptions: [{ allowSubstringInterpolation: false }],
39
+ create(context, [{ allowSubstringInterpolation }]) {
20
40
  const sourceCode = context.sourceCode;
21
41
  return {
22
42
  ['BoundAttribute Interpolation'](interpolation) {
43
+ if (allowSubstringInterpolation &&
44
+ interpolation.strings.some((str) => str !== '')) {
45
+ return;
46
+ }
23
47
  const { sourceSpan: { start, end }, } = interpolation;
24
48
  context.report({
25
49
  loc: {
@@ -0,0 +1,7 @@
1
+ declare const messageId = "preferTemplateLiteral";
2
+ export type Options = [];
3
+ export type MessageIds = typeof messageId;
4
+ export declare const RULE_NAME = "prefer-template-literal";
5
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferTemplateLiteral", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
+ export default _default;
7
+ //# sourceMappingURL=prefer-template-literal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-template-literal.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-template-literal.ts"],"names":[],"mappings":"AAeA,QAAA,MAAM,SAAS,0BAA0B,CAAC;AAE1C,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,OAAO,SAAS,CAAC;AAC1C,eAAO,MAAM,SAAS,4BAA4B,CAAC;;AAEnD,wBAwFG"}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_NAME = void 0;
4
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
5
+ const utils_1 = require("@angular-eslint/utils");
6
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ const literal_primitive_1 = require("../utils/literal-primitive");
8
+ const messageId = 'preferTemplateLiteral';
9
+ exports.RULE_NAME = 'prefer-template-literal';
10
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
11
+ name: exports.RULE_NAME,
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'Ensure that template literals are used instead of concatenating strings or expressions.',
16
+ },
17
+ fixable: 'code',
18
+ schema: [],
19
+ messages: {
20
+ preferTemplateLiteral: 'Prefer using template literal instead of concatenating strings or expressions',
21
+ },
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ (0, utils_1.ensureTemplateParser)(context);
26
+ const { sourceCode } = context;
27
+ return {
28
+ 'Binary[operation="+"]'(node) {
29
+ const { left, right } = node;
30
+ const isLeftString = (0, literal_primitive_1.isStringLiteralPrimitive)(left) || left instanceof bundled_angular_compiler_1.TemplateLiteral;
31
+ const isRightString = (0, literal_primitive_1.isStringLiteralPrimitive)(right) || right instanceof bundled_angular_compiler_1.TemplateLiteral;
32
+ // If both sides are not strings, we don't report anything
33
+ if (!isLeftString && !isRightString) {
34
+ return;
35
+ }
36
+ const { sourceSpan: { start, end }, } = node;
37
+ function getQuote() {
38
+ const leftValue = sourceCode.text.at(left.sourceSpan.start);
39
+ if (leftValue === "'" || leftValue === '"') {
40
+ return leftValue;
41
+ }
42
+ const rightValue = sourceCode.text.at(right.sourceSpan.start);
43
+ if (rightValue === "'" || rightValue === '"') {
44
+ return rightValue;
45
+ }
46
+ return '`';
47
+ }
48
+ context.report({
49
+ loc: {
50
+ start: sourceCode.getLocFromIndex(start),
51
+ end: sourceCode.getLocFromIndex(end),
52
+ },
53
+ messageId,
54
+ fix: (fixer) => {
55
+ // If both sides are literals, we remove the `+` sign, escape if necessary and concatenate them
56
+ if (left instanceof bundled_angular_compiler_1.LiteralPrimitive &&
57
+ right instanceof bundled_angular_compiler_1.LiteralPrimitive) {
58
+ const quote = getQuote();
59
+ return fixer.replaceTextRange([start, end], `${quote}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, quote)}${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, quote)}${quote}`);
60
+ }
61
+ const fixes = new Array();
62
+ // Fix the left side
63
+ fixes.push(...getLeftSideFixes(fixer, left));
64
+ // Remove the `+` sign
65
+ fixes.push(fixer.removeRange([left.sourceSpan.end, right.sourceSpan.start]));
66
+ // Fix the right side
67
+ fixes.push(...getRightSideFixes(fixer, right));
68
+ return fixes;
69
+ },
70
+ });
71
+ },
72
+ };
73
+ },
74
+ });
75
+ function getLeftSideFixes(fixer, left) {
76
+ const { start, end } = left.sourceSpan;
77
+ if (left instanceof bundled_angular_compiler_1.TemplateLiteral) {
78
+ // Remove the end ` sign from the left side
79
+ return [fixer.removeRange([end - 1, end])];
80
+ }
81
+ else if ((0, literal_primitive_1.isLiteralPrimitive)(left)) {
82
+ // Transform left side to template literal
83
+ return [
84
+ fixer.replaceTextRange([start, end], `\`${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(left, '`')}`),
85
+ ];
86
+ }
87
+ else {
88
+ // Transform left side to template literal
89
+ return [
90
+ fixer.insertTextBeforeRange([start, end], '`${'),
91
+ fixer.insertTextAfterRange([start, end], '}'),
92
+ ];
93
+ }
94
+ }
95
+ function getRightSideFixes(fixer, right) {
96
+ const { start, end } = right.sourceSpan;
97
+ if (right instanceof bundled_angular_compiler_1.TemplateLiteral) {
98
+ // Remove the start ` sign from the right side
99
+ return [fixer.removeRange([start, start + 1])];
100
+ }
101
+ else if ((0, literal_primitive_1.isLiteralPrimitive)(right)) {
102
+ // Transform right side to template literal if it's a string
103
+ return [
104
+ fixer.replaceTextRange([start, end], `${(0, literal_primitive_1.getLiteralPrimitiveStringValue)(right, '`')}\``),
105
+ ];
106
+ }
107
+ else {
108
+ // Transform right side to template literal
109
+ return [
110
+ fixer.insertTextBeforeRange([start, end], '${'),
111
+ fixer.insertTextAfterRange([start, end], '}`'),
112
+ ];
113
+ }
114
+ }
@@ -0,0 +1,7 @@
1
+ import { AST, LiteralPrimitive } from '@angular-eslint/bundled-angular-compiler';
2
+ export declare function isLiteralPrimitive(node: AST): node is LiteralPrimitive;
3
+ export declare function isStringLiteralPrimitive(node: AST): node is Omit<LiteralPrimitive, 'value'> & {
4
+ value: string;
5
+ };
6
+ export declare function getLiteralPrimitiveStringValue(node: LiteralPrimitive, quote: "'" | '"' | '`'): string;
7
+ //# sourceMappingURL=literal-primitive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literal-primitive.d.ts","sourceRoot":"","sources":["../../src/utils/literal-primitive.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,gBAAgB,EACjB,MAAM,0CAA0C,CAAC;AAElD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,IAAI,gBAAgB,CAEtE;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,GAAG,GACR,IAAI,IAAI,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAE7D;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GACrB,MAAM,CAIR"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLiteralPrimitive = isLiteralPrimitive;
4
+ exports.isStringLiteralPrimitive = isStringLiteralPrimitive;
5
+ exports.getLiteralPrimitiveStringValue = getLiteralPrimitiveStringValue;
6
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
7
+ function isLiteralPrimitive(node) {
8
+ return node instanceof bundled_angular_compiler_1.LiteralPrimitive;
9
+ }
10
+ function isStringLiteralPrimitive(node) {
11
+ return isLiteralPrimitive(node) && typeof node.value === 'string';
12
+ }
13
+ function getLiteralPrimitiveStringValue(node, quote) {
14
+ return typeof node.value === 'string'
15
+ ? `${node.value.replaceAll(quote, `\\${quote}`)}`
16
+ : String(node.value);
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "19.3.1-alpha.9",
3
+ "version": "19.4.0",
4
4
  "description": "ESLint plugin for Angular Templates",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,13 +20,13 @@
20
20
  "dependencies": {
21
21
  "aria-query": "5.3.2",
22
22
  "axobject-query": "4.1.0",
23
- "@angular-eslint/bundled-angular-compiler": "19.3.1-alpha.9",
24
- "@angular-eslint/utils": "19.3.1-alpha.9"
23
+ "@angular-eslint/bundled-angular-compiler": "19.4.0",
24
+ "@angular-eslint/utils": "19.4.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/template-parser": "19.3.1-alpha.9",
29
- "@angular-eslint/test-utils": "19.3.1-alpha.9"
28
+ "@angular-eslint/template-parser": "19.4.0",
29
+ "@angular-eslint/test-utils": "19.4.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@typescript-eslint/types": "^7.11.0 || ^8.0.0",