@taiga-ui/eslint-plugin-experience-next 0.471.0 → 0.472.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.
Files changed (3) hide show
  1. package/README.md +15 -7
  2. package/index.esm.js +93 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,7 +44,7 @@ export default [
44
44
  | decorator-key-sort | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
45
45
  | flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
46
46
  | host-attributes-sort | Sort Angular host metadata attributes using configurable attribute groups | ✅ | 🔧 | |
47
- | injection-token-description | They are required to provide a description for `InjectionToken` | ✅ | | |
47
+ | injection-token-description | Require `InjectionToken` descriptions to include the token name | ✅ | 🔧 | |
48
48
  | no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
49
49
  | no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
50
50
  | no-fully-untracked-effect | Disallow reactive callbacks where all signal reads are hidden inside `untracked()` | ✅ | | |
@@ -282,17 +282,25 @@ Use atomic presets when you want a custom order instead of one of the bundled al
282
282
 
283
283
  ## injection-token-description
284
284
 
285
- <sup>`✅ Recommended`</sup>
285
+ <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
286
286
 
287
- The description string passed to `new InjectionToken(...)` must contain the name of the variable it is assigned to. This
288
- makes token names visible in Angular DevTools and error messages.
287
+ The description passed to `new InjectionToken(...)` must contain the name of the variable it is assigned to. The rule
288
+ accepts both direct string descriptions and Angular's `ngDevMode ? '...' : ''` pattern, and the autofix rewrites invalid
289
+ descriptions to the dev-only form. If `ngDevMode` is not declared in the file, the autofix inserts
290
+ `declare const ngDevMode: boolean;` after imports.
289
291
 
290
292
  ```ts
291
- // ❌ error — description does not mention TUI_MY_TOKEN
292
- const TUI_MY_TOKEN = new InjectionToken<string>('some description');
293
+ // ❌ error
294
+ import {InjectionToken} from '@angular/core';
295
+
296
+ export const TUI_MY_TOKEN = new InjectionToken<string>('some description');
293
297
 
294
298
  // ✅ after autofix
295
- const TUI_MY_TOKEN = new InjectionToken<string>('[TUI_MY_TOKEN]: some description');
299
+ import {InjectionToken} from '@angular/core';
300
+
301
+ declare const ngDevMode: boolean;
302
+
303
+ export const TUI_MY_TOKEN = new InjectionToken<string>(ngDevMode ? '[TUI_MY_TOKEN]: some description' : '');
296
304
  ```
297
305
 
298
306
  ---
package/index.esm.js CHANGED
@@ -2024,37 +2024,109 @@ const config$2 = {
2024
2024
 
2025
2025
  const MESSAGE_ID$5 = 'invalid-injection-token-description';
2026
2026
  const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
2027
+ const NG_DEV_MODE = 'ngDevMode';
2027
2028
  const createRule$e = ESLintUtils.RuleCreator((name) => name);
2029
+ function getVariableName(node) {
2030
+ if (node.parent.type !== AST_NODE_TYPES$1.VariableDeclarator) {
2031
+ return undefined;
2032
+ }
2033
+ const { id } = node.parent;
2034
+ return id.type === AST_NODE_TYPES$1.Identifier ? id.name : undefined;
2035
+ }
2036
+ function isStringLiteral$1(node) {
2037
+ return node.type === AST_NODE_TYPES$1.Literal && typeof node.value === 'string';
2038
+ }
2039
+ function isStringLike(node) {
2040
+ return isStringLiteral$1(node) || node.type === AST_NODE_TYPES$1.TemplateLiteral;
2041
+ }
2042
+ function getStringValue(node) {
2043
+ if (isStringLiteral$1(node)) {
2044
+ return node.value;
2045
+ }
2046
+ return node.quasis[0]?.value.raw || '';
2047
+ }
2048
+ function isEmptyString$1(node) {
2049
+ return (getStringValue(node) === '' &&
2050
+ (!('expressions' in node) || !node.expressions.length));
2051
+ }
2052
+ function isNgDevModeConditional(node) {
2053
+ return (node.type === AST_NODE_TYPES$1.ConditionalExpression &&
2054
+ node.test.type === AST_NODE_TYPES$1.Identifier &&
2055
+ node.test.name === NG_DEV_MODE &&
2056
+ isStringLike(node.consequent) &&
2057
+ isStringLike(node.alternate) &&
2058
+ isEmptyString$1(node.alternate));
2059
+ }
2060
+ function getDescriptionValue(node) {
2061
+ if (isStringLike(node)) {
2062
+ return getStringValue(node);
2063
+ }
2064
+ if (isNgDevModeConditional(node)) {
2065
+ return getStringValue(node.consequent);
2066
+ }
2067
+ return undefined;
2068
+ }
2069
+ function getDescriptionNode(node) {
2070
+ if (isStringLike(node)) {
2071
+ return node;
2072
+ }
2073
+ return isNgDevModeConditional(node) ? node.consequent : undefined;
2074
+ }
2075
+ function prependTokenName(text, name) {
2076
+ return `${text.slice(0, 1)}[${name}]: ${text.slice(1)}`;
2077
+ }
2078
+ function isNgDevModeVisible(sourceCode, node) {
2079
+ for (let scope = sourceCode.getScope(node); scope !== null; scope = scope.upper) {
2080
+ if (scope.variables.some((variable) => variable.name === NG_DEV_MODE)) {
2081
+ return true;
2082
+ }
2083
+ }
2084
+ return false;
2085
+ }
2086
+ function getNgDevModeDeclarationFix(program, fixer) {
2087
+ const lastImport = [...program.body]
2088
+ .reverse()
2089
+ .find((statement) => statement.type === AST_NODE_TYPES$1.ImportDeclaration);
2090
+ if (lastImport) {
2091
+ return fixer.insertTextAfter(lastImport, '\n\ndeclare const ngDevMode: boolean;');
2092
+ }
2093
+ const [firstStatement] = program.body;
2094
+ if (firstStatement) {
2095
+ return fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n');
2096
+ }
2097
+ return fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
2098
+ }
2028
2099
  const rule$h = createRule$e({
2029
2100
  create(context) {
2101
+ const { sourceCode } = context;
2102
+ const program = sourceCode.ast;
2103
+ let shouldAddNgDevModeDeclaration = true;
2030
2104
  return {
2031
2105
  'NewExpression[callee.name="InjectionToken"]'(node) {
2032
- let token;
2033
- let name;
2034
- const [description] = node?.arguments ?? [];
2035
- if (!description) {
2106
+ const [description] = node.arguments;
2107
+ if (!description || description.type === AST_NODE_TYPES$1.SpreadElement) {
2036
2108
  return;
2037
2109
  }
2038
- const isLiteral = description.type === AST_NODE_TYPES$1.Literal &&
2039
- typeof description.value === 'string';
2040
- if (isLiteral) {
2041
- token = description.value;
2042
- }
2043
- if (description.type === AST_NODE_TYPES$1.TemplateLiteral) {
2044
- token = description.quasis[0]?.value.raw || '';
2045
- }
2046
- if (node?.parent.type === AST_NODE_TYPES$1.VariableDeclarator) {
2047
- const id = node.parent.id;
2048
- if (id.type === AST_NODE_TYPES$1.Identifier) {
2049
- name = id.name;
2050
- }
2051
- }
2110
+ const name = getVariableName(node);
2111
+ const token = getDescriptionValue(description);
2112
+ const fixedDescription = getDescriptionNode(description);
2052
2113
  const report = name && token && !token.includes(name);
2053
- if (report) {
2114
+ if (report && fixedDescription) {
2054
2115
  context.report({
2055
2116
  fix: (fixer) => {
2056
- const [start, end] = description.range;
2057
- return fixer.insertTextBeforeRange([start + 1, end], `[${name}]: `);
2117
+ const isNgDevModeGuarded = isNgDevModeConditional(description);
2118
+ const fixes = [
2119
+ fixer.replaceText(isNgDevModeGuarded ? fixedDescription : description, isNgDevModeGuarded
2120
+ ? prependTokenName(sourceCode.getText(fixedDescription), name)
2121
+ : `${NG_DEV_MODE} ? ${prependTokenName(sourceCode.getText(fixedDescription), name)} : ''`),
2122
+ ];
2123
+ if (!isNgDevModeGuarded &&
2124
+ shouldAddNgDevModeDeclaration &&
2125
+ !isNgDevModeVisible(sourceCode, description)) {
2126
+ shouldAddNgDevModeDeclaration = false;
2127
+ fixes.unshift(getNgDevModeDeclarationFix(program, fixer));
2128
+ }
2129
+ return fixes;
2058
2130
  },
2059
2131
  messageId: MESSAGE_ID$5,
2060
2132
  node: description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.471.0",
3
+ "version": "0.472.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",