@taiga-ui/eslint-plugin-experience-next 0.513.0 → 0.515.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 +2 -0
- package/configs/utils/get-ng-version.d.ts +1 -0
- package/index.d.ts +6 -0
- package/index.esm.js +510 -127
- package/package.json +5 -1
- package/rules/recommended/no-nested-interactive.d.ts +5 -0
- package/rules/recommended/no-nested-ternary-in-template.d.ts +5 -0
- package/rules/utils/angular/element-attributes.d.ts +7 -0
- package/rules/utils/angular/interactive-elements.d.ts +2 -0
- package/rules/utils/angular/source-span.d.ts +3 -1
- package/rules/utils/angular/template-attributes.d.ts +5 -0
- package/rules/utils/angular/template-expressions.d.ts +4 -0
- package/rules/utils/angular/template-identifiers.d.ts +10 -0
- package/rules/utils/ast/identifiers.d.ts +2 -0
- package/rules/utils/ast/spacing.d.ts +1 -0
package/README.md
CHANGED
|
@@ -95,6 +95,8 @@ from third-party plugins. The exact severities and file globs live in
|
|
|
95
95
|
| [no-implicit-public](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-implicit-public.md) | Require explicit `public` modifier for class members and parameter properties | ✅ | 🔧 | |
|
|
96
96
|
| [no-infinite-loop](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-infinite-loop.md) | Disallow `while (true)` and `for` loops without an explicit condition | ✅ | | |
|
|
97
97
|
| [no-legacy-peer-deps](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-legacy-peer-deps.md) | Disallow `legacy-peer-deps=true` in `.npmrc` | ✅ | | |
|
|
98
|
+
| [no-nested-interactive](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-nested-interactive.md) | Disallow interactive HTML elements nested inside other interactive elements | ✅ | | |
|
|
99
|
+
| [no-nested-ternary-in-template](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-nested-ternary-in-template.md) | Disallow nested ternary expressions in Angular 19+ templates | ✅ | 🔧 | |
|
|
98
100
|
| [no-obsolete-attrs](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-obsolete-attrs.md) | Disallow obsolete HTML attributes | ✅ | | |
|
|
99
101
|
| [no-obsolete-tags](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-obsolete-tags.md) | Disallow obsolete HTML tags | ✅ | | |
|
|
100
102
|
| [no-playwright-empty-fill](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-playwright-empty-fill.md) | Enforce `clear()` over `fill('')` in Playwright tests | ✅ | 🔧 | |
|
package/index.d.ts
CHANGED
|
@@ -99,6 +99,12 @@ declare const plugin: {
|
|
|
99
99
|
'no-legacy-peer-deps': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noLegacyPeerDeps", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
100
100
|
name: string;
|
|
101
101
|
};
|
|
102
|
+
'no-nested-interactive': import("eslint").Rule.RuleModule & {
|
|
103
|
+
name: string;
|
|
104
|
+
};
|
|
105
|
+
'no-nested-ternary-in-template': import("eslint").Rule.RuleModule & {
|
|
106
|
+
name: string;
|
|
107
|
+
};
|
|
102
108
|
'no-obsolete-attrs': import("eslint").Rule.RuleModule & {
|
|
103
109
|
name: string;
|
|
104
110
|
};
|
package/index.esm.js
CHANGED
|
@@ -514,6 +514,7 @@ const modernAngularRules = {
|
|
|
514
514
|
modernStyles: 17,
|
|
515
515
|
preferControlFlow: 17,
|
|
516
516
|
preferSignals: 17,
|
|
517
|
+
templateLet: 19,
|
|
517
518
|
templateLiteral: 19,
|
|
518
519
|
};
|
|
519
520
|
|
|
@@ -1309,6 +1310,8 @@ var recommended = defineConfig([
|
|
|
1309
1310
|
'@taiga-ui/experience-next/no-duplicate-id': 'error',
|
|
1310
1311
|
'@taiga-ui/experience-next/no-duplicate-in-head': 'error',
|
|
1311
1312
|
'@taiga-ui/experience-next/no-href-with-router-link': 'error',
|
|
1313
|
+
'@taiga-ui/experience-next/no-nested-interactive': 'error',
|
|
1314
|
+
'@taiga-ui/experience-next/no-nested-ternary-in-template': angularVersion >= modernAngularRules.templateLet ? 'error' : 'off',
|
|
1312
1315
|
'@taiga-ui/experience-next/no-obsolete-attrs': 'error',
|
|
1313
1316
|
'@taiga-ui/experience-next/no-obsolete-tags': 'error',
|
|
1314
1317
|
'@taiga-ui/experience-next/no-project-as-in-ng-template': 'error',
|
|
@@ -33212,9 +33215,44 @@ __name(compileHmrUpdateCallback, "compileHmrUpdateCallback");
|
|
|
33212
33215
|
var VERSION = new Version("20.3.12");
|
|
33213
33216
|
publishFacade(_global);
|
|
33214
33217
|
|
|
33218
|
+
function getAttributeNames(name) {
|
|
33219
|
+
return typeof name === 'string'
|
|
33220
|
+
? [name.toLowerCase()]
|
|
33221
|
+
: name.map((item) => item.toLowerCase());
|
|
33222
|
+
}
|
|
33223
|
+
function isMatchingAttributeName(name, names) {
|
|
33224
|
+
return names.includes(name.toLowerCase());
|
|
33225
|
+
}
|
|
33215
33226
|
function getAttributeValueSpan(attr) {
|
|
33216
33227
|
return attr instanceof dist$4.TmplAstBoundEvent ? attr.handlerSpan : attr.valueSpan;
|
|
33217
33228
|
}
|
|
33229
|
+
function getStaticAttribute(element, name) {
|
|
33230
|
+
const attrName = name.toLowerCase();
|
|
33231
|
+
return element.attributes.find((attr) => attr.name.toLowerCase() === attrName);
|
|
33232
|
+
}
|
|
33233
|
+
function getStaticAttributeValue(element, name) {
|
|
33234
|
+
return getStaticAttribute(element, name)?.value;
|
|
33235
|
+
}
|
|
33236
|
+
function hasInputBinding(element, name) {
|
|
33237
|
+
const names = getAttributeNames(name);
|
|
33238
|
+
return element.inputs.some((input) => isMatchingAttributeName(input.name, names));
|
|
33239
|
+
}
|
|
33240
|
+
function hasElementAttribute(element, name) {
|
|
33241
|
+
const names = getAttributeNames(name);
|
|
33242
|
+
return (element.attributes.some((attr) => isMatchingAttributeName(attr.name, names)) ||
|
|
33243
|
+
element.inputs.some((input) => {
|
|
33244
|
+
if (isMatchingAttributeName(input.name, names)) {
|
|
33245
|
+
return true;
|
|
33246
|
+
}
|
|
33247
|
+
const details = input.keySpan.details?.toLowerCase();
|
|
33248
|
+
return (details?.startsWith('attr.') === true && names.includes(details.slice(5)));
|
|
33249
|
+
}));
|
|
33250
|
+
}
|
|
33251
|
+
function hasOutputBinding(element, name) {
|
|
33252
|
+
{
|
|
33253
|
+
return element.outputs.length > 0;
|
|
33254
|
+
}
|
|
33255
|
+
}
|
|
33218
33256
|
function getElementAttributeLikes(element) {
|
|
33219
33257
|
const seen = new Set();
|
|
33220
33258
|
return [
|
|
@@ -33246,6 +33284,12 @@ function sourceSpanToLoc(span) {
|
|
|
33246
33284
|
},
|
|
33247
33285
|
};
|
|
33248
33286
|
}
|
|
33287
|
+
function containsAbsoluteSourceSpan(container, child) {
|
|
33288
|
+
return container.start <= child.start && child.end <= container.end;
|
|
33289
|
+
}
|
|
33290
|
+
function getAbsoluteSourceSpanText(text, span) {
|
|
33291
|
+
return text.slice(span.start, span.end);
|
|
33292
|
+
}
|
|
33249
33293
|
|
|
33250
33294
|
var dist$3 = {};
|
|
33251
33295
|
|
|
@@ -46233,7 +46277,7 @@ function buildMultilineStartTag(node, sourceText) {
|
|
|
46233
46277
|
closing,
|
|
46234
46278
|
].join('\n');
|
|
46235
46279
|
}
|
|
46236
|
-
const rule$
|
|
46280
|
+
const rule$V = createRule({
|
|
46237
46281
|
name: 'attrs-newline',
|
|
46238
46282
|
rule: {
|
|
46239
46283
|
create(context) {
|
|
@@ -46413,7 +46457,7 @@ function sameOrder(a, b) {
|
|
|
46413
46457
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
46414
46458
|
}
|
|
46415
46459
|
|
|
46416
|
-
const rule$
|
|
46460
|
+
const rule$U = createRule({
|
|
46417
46461
|
create(context, [order]) {
|
|
46418
46462
|
const decorators = new Set(Object.keys(order));
|
|
46419
46463
|
return {
|
|
@@ -46525,7 +46569,7 @@ const INLINE_HTML_ELEMENTS = new Set([
|
|
|
46525
46569
|
'wbr',
|
|
46526
46570
|
]);
|
|
46527
46571
|
|
|
46528
|
-
const MESSAGE_ID$
|
|
46572
|
+
const MESSAGE_ID$i = 'expectAfter';
|
|
46529
46573
|
const SKIP_CHILDREN = new Set(['code', 'pre']);
|
|
46530
46574
|
function isChildNode(node) {
|
|
46531
46575
|
return (node instanceof dist$4.TmplAstBoundText ||
|
|
@@ -46549,7 +46593,7 @@ function getNodeLabel(node) {
|
|
|
46549
46593
|
}
|
|
46550
46594
|
return node instanceof dist$4.TmplAstBoundText ? 'binding' : 'text';
|
|
46551
46595
|
}
|
|
46552
|
-
const rule$
|
|
46596
|
+
const rule$T = createRule({
|
|
46553
46597
|
name: 'element-newline',
|
|
46554
46598
|
rule: {
|
|
46555
46599
|
create(context) {
|
|
@@ -46575,7 +46619,7 @@ const rule$R = createRule({
|
|
|
46575
46619
|
firstChild.sourceSpan.start.offset,
|
|
46576
46620
|
], '\n'),
|
|
46577
46621
|
loc: sourceSpanToLoc(firstChild.sourceSpan),
|
|
46578
|
-
messageId: MESSAGE_ID$
|
|
46622
|
+
messageId: MESSAGE_ID$i,
|
|
46579
46623
|
});
|
|
46580
46624
|
return;
|
|
46581
46625
|
}
|
|
@@ -46593,7 +46637,7 @@ const rule$R = createRule({
|
|
|
46593
46637
|
child.sourceSpan.end.offset,
|
|
46594
46638
|
], '\n'),
|
|
46595
46639
|
loc: sourceSpanToLoc(next.sourceSpan),
|
|
46596
|
-
messageId: MESSAGE_ID$
|
|
46640
|
+
messageId: MESSAGE_ID$i,
|
|
46597
46641
|
});
|
|
46598
46642
|
return;
|
|
46599
46643
|
}
|
|
@@ -46607,7 +46651,7 @@ const rule$R = createRule({
|
|
|
46607
46651
|
lastChild.sourceSpan.end.offset,
|
|
46608
46652
|
], '\n'),
|
|
46609
46653
|
loc: sourceSpanToLoc(lastChild.sourceSpan),
|
|
46610
|
-
messageId: MESSAGE_ID$
|
|
46654
|
+
messageId: MESSAGE_ID$i,
|
|
46611
46655
|
});
|
|
46612
46656
|
}
|
|
46613
46657
|
},
|
|
@@ -46616,7 +46660,7 @@ const rule$R = createRule({
|
|
|
46616
46660
|
meta: {
|
|
46617
46661
|
docs: { description: 'Enforce line breaks between block-level child nodes' },
|
|
46618
46662
|
fixable: 'code',
|
|
46619
|
-
messages: { [MESSAGE_ID$
|
|
46663
|
+
messages: { [MESSAGE_ID$i]: 'There should be a linebreak after {{name}}.' },
|
|
46620
46664
|
schema: [],
|
|
46621
46665
|
type: 'layout',
|
|
46622
46666
|
},
|
|
@@ -46688,7 +46732,7 @@ const PRESETS = {
|
|
|
46688
46732
|
$VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
|
|
46689
46733
|
$VUE_ATTRIBUTE: /^v-/,
|
|
46690
46734
|
};
|
|
46691
|
-
const rule$
|
|
46735
|
+
const rule$S = createRule({
|
|
46692
46736
|
create(context, [options]) {
|
|
46693
46737
|
const sourceCode = context.sourceCode;
|
|
46694
46738
|
const settings = {
|
|
@@ -46968,8 +47012,8 @@ const DIRECTIONAL_TO_LOGICAL = {
|
|
|
46968
47012
|
top: 'inset-block-start',
|
|
46969
47013
|
};
|
|
46970
47014
|
const STYLE_PREFIX = 'style.';
|
|
46971
|
-
const MESSAGE_ID$
|
|
46972
|
-
const MESSAGE = `
|
|
47015
|
+
const MESSAGE_ID$h = 'html-logical-properties';
|
|
47016
|
+
const MESSAGE$1 = `
|
|
46973
47017
|
Use logical CSS properties instead of directional properties. Replace:
|
|
46974
47018
|
• left → inset-inline-start
|
|
46975
47019
|
• right → inset-inline-end
|
|
@@ -47006,20 +47050,20 @@ const config$4 = {
|
|
|
47006
47050
|
context.report({
|
|
47007
47051
|
fix: (fixer) => fixer.replaceTextRange([propertyStart, propertyEnd], logicalProperty),
|
|
47008
47052
|
loc: sourceSpanToLoc(keySpan),
|
|
47009
|
-
messageId: MESSAGE_ID$
|
|
47053
|
+
messageId: MESSAGE_ID$h,
|
|
47010
47054
|
});
|
|
47011
47055
|
},
|
|
47012
47056
|
};
|
|
47013
47057
|
},
|
|
47014
47058
|
meta: {
|
|
47015
|
-
docs: { description: MESSAGE },
|
|
47059
|
+
docs: { description: MESSAGE$1 },
|
|
47016
47060
|
fixable: 'code',
|
|
47017
|
-
messages: { [MESSAGE_ID$
|
|
47061
|
+
messages: { [MESSAGE_ID$h]: MESSAGE$1 },
|
|
47018
47062
|
schema: [],
|
|
47019
47063
|
type: 'suggestion',
|
|
47020
47064
|
},
|
|
47021
47065
|
};
|
|
47022
|
-
const rule$
|
|
47066
|
+
const rule$R = createRule({
|
|
47023
47067
|
name: 'html-logical-properties',
|
|
47024
47068
|
rule: config$4,
|
|
47025
47069
|
});
|
|
@@ -248261,7 +248305,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248261
248305
|
}
|
|
248262
248306
|
return hasSafeRuntimeUsage;
|
|
248263
248307
|
}
|
|
248264
|
-
const rule$
|
|
248308
|
+
const rule$Q = createRule({
|
|
248265
248309
|
create(context) {
|
|
248266
248310
|
const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
|
|
248267
248311
|
const checkCycles = context.options[0]?.checkCycles ?? true;
|
|
@@ -248923,7 +248967,7 @@ const rule$O = createRule({
|
|
|
248923
248967
|
name: 'import-integrity',
|
|
248924
248968
|
});
|
|
248925
248969
|
|
|
248926
|
-
const MESSAGE_ID$
|
|
248970
|
+
const MESSAGE_ID$g = 'invalid-injection-token-description';
|
|
248927
248971
|
const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
|
|
248928
248972
|
const NG_DEV_MODE = 'ngDevMode';
|
|
248929
248973
|
function getVariableName(node) {
|
|
@@ -248986,7 +249030,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
|
|
|
248986
249030
|
? fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n')
|
|
248987
249031
|
: fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
|
|
248988
249032
|
}
|
|
248989
|
-
const rule$
|
|
249033
|
+
const rule$P = createRule({
|
|
248990
249034
|
create(context) {
|
|
248991
249035
|
const { sourceCode } = context;
|
|
248992
249036
|
const program = sourceCode.ast;
|
|
@@ -249018,7 +249062,7 @@ const rule$N = createRule({
|
|
|
249018
249062
|
}
|
|
249019
249063
|
return fixes;
|
|
249020
249064
|
},
|
|
249021
|
-
messageId: MESSAGE_ID$
|
|
249065
|
+
messageId: MESSAGE_ID$g,
|
|
249022
249066
|
node: description,
|
|
249023
249067
|
});
|
|
249024
249068
|
}
|
|
@@ -249028,14 +249072,14 @@ const rule$N = createRule({
|
|
|
249028
249072
|
meta: {
|
|
249029
249073
|
docs: { description: ERROR_MESSAGE$3 },
|
|
249030
249074
|
fixable: 'code',
|
|
249031
|
-
messages: { [MESSAGE_ID$
|
|
249075
|
+
messages: { [MESSAGE_ID$g]: ERROR_MESSAGE$3 },
|
|
249032
249076
|
schema: [],
|
|
249033
249077
|
type: 'problem',
|
|
249034
249078
|
},
|
|
249035
249079
|
name: 'injection-token-description',
|
|
249036
249080
|
});
|
|
249037
249081
|
|
|
249038
|
-
const rule$
|
|
249082
|
+
const rule$O = createRule({
|
|
249039
249083
|
create(context) {
|
|
249040
249084
|
const { sourceCode } = context;
|
|
249041
249085
|
const namespaceImports = new Map();
|
|
@@ -249111,7 +249155,7 @@ const rule$M = createRule({
|
|
|
249111
249155
|
name: 'no-commonjs-import-patterns',
|
|
249112
249156
|
});
|
|
249113
249157
|
|
|
249114
|
-
const MESSAGE_ID$
|
|
249158
|
+
const MESSAGE_ID$f = 'no-deep-imports';
|
|
249115
249159
|
const ERROR_MESSAGE$2 = 'Deep imports of Taiga UI packages are prohibited';
|
|
249116
249160
|
const CODE_EXTENSIONS = new Set([
|
|
249117
249161
|
'.cjs',
|
|
@@ -249130,7 +249174,7 @@ const DEFAULT_OPTIONS = {
|
|
|
249130
249174
|
importDeclaration: '^@taiga-ui*',
|
|
249131
249175
|
projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
|
|
249132
249176
|
};
|
|
249133
|
-
const rule$
|
|
249177
|
+
const rule$N = createRule({
|
|
249134
249178
|
create(context) {
|
|
249135
249179
|
const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
|
|
249136
249180
|
const hasNonCodeExtension = (source) => {
|
|
@@ -249172,7 +249216,7 @@ const rule$L = createRule({
|
|
|
249172
249216
|
const [start, end] = node.source.range;
|
|
249173
249217
|
return fixer.replaceTextRange([start + 1, end - 1], importSource.replaceAll(new RegExp(deepImport, 'g'), ''));
|
|
249174
249218
|
},
|
|
249175
|
-
messageId: MESSAGE_ID$
|
|
249219
|
+
messageId: MESSAGE_ID$f,
|
|
249176
249220
|
node: node.source,
|
|
249177
249221
|
});
|
|
249178
249222
|
},
|
|
@@ -249182,7 +249226,7 @@ const rule$L = createRule({
|
|
|
249182
249226
|
defaultOptions: [DEFAULT_OPTIONS],
|
|
249183
249227
|
docs: { description: ERROR_MESSAGE$2 },
|
|
249184
249228
|
fixable: 'code',
|
|
249185
|
-
messages: { [MESSAGE_ID$
|
|
249229
|
+
messages: { [MESSAGE_ID$f]: ERROR_MESSAGE$2 },
|
|
249186
249230
|
schema: [
|
|
249187
249231
|
{
|
|
249188
249232
|
additionalProperties: false,
|
|
@@ -249222,7 +249266,7 @@ const nearestFileUpCache = new Map();
|
|
|
249222
249266
|
const markerCache = new Map();
|
|
249223
249267
|
const indexFileCache = new Map();
|
|
249224
249268
|
const indexExportsCache = new Map();
|
|
249225
|
-
const rule$
|
|
249269
|
+
const rule$M = createRule({
|
|
249226
249270
|
create(context) {
|
|
249227
249271
|
const parserServices = dist$3.ESLintUtils.getParserServices(context);
|
|
249228
249272
|
const program = parserServices.program;
|
|
@@ -249416,13 +249460,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
|
|
|
249416
249460
|
if (!noDuplicateAttributesRule) {
|
|
249417
249461
|
throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
|
|
249418
249462
|
}
|
|
249419
|
-
const rule$
|
|
249463
|
+
const rule$L = createRule({
|
|
249420
249464
|
name: 'no-duplicate-attrs',
|
|
249421
249465
|
rule: noDuplicateAttributesRule,
|
|
249422
249466
|
});
|
|
249423
249467
|
|
|
249424
|
-
const MESSAGE_ID$
|
|
249425
|
-
const rule$
|
|
249468
|
+
const MESSAGE_ID$e = 'duplicateId';
|
|
249469
|
+
const rule$K = createRule({
|
|
249426
249470
|
name: 'no-duplicate-id',
|
|
249427
249471
|
rule: {
|
|
249428
249472
|
create(context) {
|
|
@@ -249445,7 +249489,7 @@ const rule$I = createRule({
|
|
|
249445
249489
|
context.report({
|
|
249446
249490
|
data: { id },
|
|
249447
249491
|
loc: sourceSpanToLoc(attr.sourceSpan),
|
|
249448
|
-
messageId: MESSAGE_ID$
|
|
249492
|
+
messageId: MESSAGE_ID$e,
|
|
249449
249493
|
});
|
|
249450
249494
|
}
|
|
249451
249495
|
}
|
|
@@ -249454,36 +249498,33 @@ const rule$I = createRule({
|
|
|
249454
249498
|
},
|
|
249455
249499
|
meta: {
|
|
249456
249500
|
docs: { description: 'Disallow duplicate static id attributes' },
|
|
249457
|
-
messages: { [MESSAGE_ID$
|
|
249501
|
+
messages: { [MESSAGE_ID$e]: "The id '{{id}}' is duplicated." },
|
|
249458
249502
|
schema: [],
|
|
249459
249503
|
type: 'problem',
|
|
249460
249504
|
},
|
|
249461
249505
|
},
|
|
249462
249506
|
});
|
|
249463
249507
|
|
|
249464
|
-
const MESSAGE_ID$
|
|
249465
|
-
function findAttr(node, attrName) {
|
|
249466
|
-
return node.attributes.find((attr) => attr.name === attrName);
|
|
249467
|
-
}
|
|
249508
|
+
const MESSAGE_ID$d = 'duplicateTag';
|
|
249468
249509
|
function getTrackingKey(node) {
|
|
249469
249510
|
if (node.name === 'title' || node.name === 'base') {
|
|
249470
249511
|
return node.name;
|
|
249471
249512
|
}
|
|
249472
249513
|
if (node.name === 'meta') {
|
|
249473
|
-
if (
|
|
249514
|
+
if (getStaticAttribute(node, 'charset')) {
|
|
249474
249515
|
return 'meta[charset]';
|
|
249475
249516
|
}
|
|
249476
|
-
if (
|
|
249517
|
+
if (getStaticAttribute(node, 'name')?.value === 'viewport') {
|
|
249477
249518
|
return 'meta[name=viewport]';
|
|
249478
249519
|
}
|
|
249479
249520
|
}
|
|
249480
249521
|
return node.name === 'link' &&
|
|
249481
|
-
|
|
249482
|
-
|
|
249522
|
+
getStaticAttribute(node, 'rel')?.value === 'canonical' &&
|
|
249523
|
+
getStaticAttribute(node, 'href')
|
|
249483
249524
|
? 'link[rel=canonical]'
|
|
249484
249525
|
: null;
|
|
249485
249526
|
}
|
|
249486
|
-
const rule$
|
|
249527
|
+
const rule$J = createRule({
|
|
249487
249528
|
name: 'no-duplicate-in-head',
|
|
249488
249529
|
rule: {
|
|
249489
249530
|
create(context) {
|
|
@@ -249520,7 +249561,7 @@ const rule$H = createRule({
|
|
|
249520
249561
|
context.report({
|
|
249521
249562
|
data: { tag },
|
|
249522
249563
|
loc: sourceSpanToLoc(duplicate.startSourceSpan),
|
|
249523
|
-
messageId: MESSAGE_ID$
|
|
249564
|
+
messageId: MESSAGE_ID$d,
|
|
249524
249565
|
});
|
|
249525
249566
|
}
|
|
249526
249567
|
}
|
|
@@ -249531,7 +249572,7 @@ const rule$H = createRule({
|
|
|
249531
249572
|
docs: {
|
|
249532
249573
|
description: 'Disallow duplicate title/base/meta/link tags inside head',
|
|
249533
249574
|
},
|
|
249534
|
-
messages: { [MESSAGE_ID$
|
|
249575
|
+
messages: { [MESSAGE_ID$d]: 'Duplicate <{{tag}}> tag in <head>.' },
|
|
249535
249576
|
schema: [],
|
|
249536
249577
|
type: 'problem',
|
|
249537
249578
|
},
|
|
@@ -249539,7 +249580,7 @@ const rule$H = createRule({
|
|
|
249539
249580
|
});
|
|
249540
249581
|
|
|
249541
249582
|
const COMPONENT_DECORATORS = new Set(['Component']);
|
|
249542
|
-
const rule$
|
|
249583
|
+
const rule$I = createRule({
|
|
249543
249584
|
create(context) {
|
|
249544
249585
|
const { sourceCode } = context;
|
|
249545
249586
|
return {
|
|
@@ -250187,7 +250228,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
|
|
|
250187
250228
|
const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
|
|
250188
250229
|
const createUntrackedRule = createRule;
|
|
250189
250230
|
|
|
250190
|
-
const rule$
|
|
250231
|
+
const rule$H = createUntrackedRule({
|
|
250191
250232
|
create(context) {
|
|
250192
250233
|
const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
|
|
250193
250234
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -250225,7 +250266,7 @@ const rule$F = createUntrackedRule({
|
|
|
250225
250266
|
name: 'no-fully-untracked-effect',
|
|
250226
250267
|
});
|
|
250227
250268
|
|
|
250228
|
-
const MESSAGE_ID$
|
|
250269
|
+
const MESSAGE_ID$c = 'no-href-with-router-link';
|
|
250229
250270
|
const ERROR_MESSAGE$1 = 'Do not use href and routerLink attributes together on the same element';
|
|
250230
250271
|
const config$3 = {
|
|
250231
250272
|
create(context) {
|
|
@@ -250235,9 +250276,9 @@ const config$3 = {
|
|
|
250235
250276
|
if (node.name !== 'a') {
|
|
250236
250277
|
return;
|
|
250237
250278
|
}
|
|
250238
|
-
const hrefAttr = node
|
|
250239
|
-
const hasRouterLink =
|
|
250240
|
-
node
|
|
250279
|
+
const hrefAttr = getStaticAttribute(node, 'href');
|
|
250280
|
+
const hasRouterLink = Boolean(getStaticAttribute(node, 'routerlink')) ||
|
|
250281
|
+
hasInputBinding(node, 'routerlink');
|
|
250241
250282
|
if (!hrefAttr || !hasRouterLink) {
|
|
250242
250283
|
return;
|
|
250243
250284
|
}
|
|
@@ -250247,7 +250288,7 @@ const config$3 = {
|
|
|
250247
250288
|
hrefAttr.sourceSpan.end.offset,
|
|
250248
250289
|
]),
|
|
250249
250290
|
loc: sourceSpanToLoc(hrefAttr.sourceSpan),
|
|
250250
|
-
messageId: MESSAGE_ID$
|
|
250291
|
+
messageId: MESSAGE_ID$c,
|
|
250251
250292
|
});
|
|
250252
250293
|
},
|
|
250253
250294
|
};
|
|
@@ -250255,12 +250296,12 @@ const config$3 = {
|
|
|
250255
250296
|
meta: {
|
|
250256
250297
|
docs: { description: ERROR_MESSAGE$1 },
|
|
250257
250298
|
fixable: 'code',
|
|
250258
|
-
messages: { [MESSAGE_ID$
|
|
250299
|
+
messages: { [MESSAGE_ID$c]: ERROR_MESSAGE$1 },
|
|
250259
250300
|
schema: [],
|
|
250260
250301
|
type: 'problem',
|
|
250261
250302
|
},
|
|
250262
250303
|
};
|
|
250263
|
-
const rule$
|
|
250304
|
+
const rule$G = createRule({
|
|
250264
250305
|
name: 'no-href-with-router-link',
|
|
250265
250306
|
rule: config$3,
|
|
250266
250307
|
});
|
|
@@ -250321,7 +250362,7 @@ function getScopeRoot(node) {
|
|
|
250321
250362
|
return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
|
|
250322
250363
|
}
|
|
250323
250364
|
|
|
250324
|
-
const rule$
|
|
250365
|
+
const rule$F = createRule({
|
|
250325
250366
|
create(context) {
|
|
250326
250367
|
const checkImplicitPublic = (node) => {
|
|
250327
250368
|
const classRef = getEnclosingClass(node);
|
|
@@ -250383,7 +250424,7 @@ const rule$D = createRule({
|
|
|
250383
250424
|
name: 'no-implicit-public',
|
|
250384
250425
|
});
|
|
250385
250426
|
|
|
250386
|
-
const rule$
|
|
250427
|
+
const rule$E = createRule({
|
|
250387
250428
|
create(context) {
|
|
250388
250429
|
const { sourceCode } = context;
|
|
250389
250430
|
return {
|
|
@@ -250439,7 +250480,7 @@ function isInfiniteLoopLiteral(node) {
|
|
|
250439
250480
|
function isInfiniteLoopTest(test) {
|
|
250440
250481
|
return test == null || isInfiniteLoopLiteral(test);
|
|
250441
250482
|
}
|
|
250442
|
-
const rule$
|
|
250483
|
+
const rule$D = createRule({
|
|
250443
250484
|
create(context) {
|
|
250444
250485
|
return {
|
|
250445
250486
|
DoWhileStatement(node) {
|
|
@@ -250484,7 +250525,7 @@ const rule$B = createRule({
|
|
|
250484
250525
|
});
|
|
250485
250526
|
|
|
250486
250527
|
const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
|
|
250487
|
-
const rule$
|
|
250528
|
+
const rule$C = createRule({
|
|
250488
250529
|
create(context) {
|
|
250489
250530
|
return {
|
|
250490
250531
|
Program(node) {
|
|
@@ -250522,6 +250563,395 @@ const rule$A = createRule({
|
|
|
250522
250563
|
name: 'no-legacy-peer-deps',
|
|
250523
250564
|
});
|
|
250524
250565
|
|
|
250566
|
+
function isInteractiveElement(node) {
|
|
250567
|
+
const tagName = node.name.toLowerCase();
|
|
250568
|
+
if (hasElementAttribute(node, 'tabindex') || hasOutputBinding(node)) {
|
|
250569
|
+
return true;
|
|
250570
|
+
}
|
|
250571
|
+
switch (tagName) {
|
|
250572
|
+
case 'a':
|
|
250573
|
+
return hasElementAttribute(node, ['href', 'routerLink']);
|
|
250574
|
+
case 'area':
|
|
250575
|
+
return hasElementAttribute(node, 'href');
|
|
250576
|
+
case 'audio':
|
|
250577
|
+
return hasElementAttribute(node, 'controls');
|
|
250578
|
+
case 'button':
|
|
250579
|
+
return true;
|
|
250580
|
+
case 'details':
|
|
250581
|
+
return true;
|
|
250582
|
+
case 'embed':
|
|
250583
|
+
return true;
|
|
250584
|
+
case 'iframe':
|
|
250585
|
+
return true;
|
|
250586
|
+
case 'img':
|
|
250587
|
+
return hasElementAttribute(node, 'usemap');
|
|
250588
|
+
case 'input':
|
|
250589
|
+
return (getStaticAttributeValue(node, 'type')?.trim().toLowerCase() !== 'hidden');
|
|
250590
|
+
case 'label':
|
|
250591
|
+
return true;
|
|
250592
|
+
case 'select':
|
|
250593
|
+
return true;
|
|
250594
|
+
case 'summary':
|
|
250595
|
+
return true;
|
|
250596
|
+
case 'textarea':
|
|
250597
|
+
return true;
|
|
250598
|
+
case 'video':
|
|
250599
|
+
return hasElementAttribute(node, 'controls');
|
|
250600
|
+
default:
|
|
250601
|
+
return false;
|
|
250602
|
+
}
|
|
250603
|
+
}
|
|
250604
|
+
|
|
250605
|
+
const MESSAGE_ID$b = 'noNestedInteractive';
|
|
250606
|
+
function getAvailableLabelParent(stack, node, labelsWithControl) {
|
|
250607
|
+
const parent = stack[stack.length - 1];
|
|
250608
|
+
return stack.length === 1 &&
|
|
250609
|
+
parent?.name.toLowerCase() === 'label' &&
|
|
250610
|
+
node.name.toLowerCase() !== 'label' &&
|
|
250611
|
+
!labelsWithControl.has(parent)
|
|
250612
|
+
? parent
|
|
250613
|
+
: null;
|
|
250614
|
+
}
|
|
250615
|
+
const rule$B = createRule({
|
|
250616
|
+
name: 'no-nested-interactive',
|
|
250617
|
+
rule: {
|
|
250618
|
+
create(context) {
|
|
250619
|
+
const interactiveStack = [];
|
|
250620
|
+
const labelsWithControl = new WeakSet();
|
|
250621
|
+
return {
|
|
250622
|
+
Element(rawNode) {
|
|
250623
|
+
const node = rawNode;
|
|
250624
|
+
if (!isInteractiveElement(node)) {
|
|
250625
|
+
return;
|
|
250626
|
+
}
|
|
250627
|
+
const parent = interactiveStack[interactiveStack.length - 1];
|
|
250628
|
+
const availableLabelParent = getAvailableLabelParent(interactiveStack, node, labelsWithControl);
|
|
250629
|
+
if (availableLabelParent) {
|
|
250630
|
+
labelsWithControl.add(availableLabelParent);
|
|
250631
|
+
}
|
|
250632
|
+
else if (parent) {
|
|
250633
|
+
context.report({
|
|
250634
|
+
data: { tag: parent.name },
|
|
250635
|
+
loc: sourceSpanToLoc(node.startSourceSpan),
|
|
250636
|
+
messageId: MESSAGE_ID$b,
|
|
250637
|
+
});
|
|
250638
|
+
}
|
|
250639
|
+
interactiveStack.push(node);
|
|
250640
|
+
},
|
|
250641
|
+
'Element:exit'(rawNode) {
|
|
250642
|
+
const node = rawNode;
|
|
250643
|
+
if (interactiveStack[interactiveStack.length - 1] === node) {
|
|
250644
|
+
interactiveStack.pop();
|
|
250645
|
+
}
|
|
250646
|
+
},
|
|
250647
|
+
};
|
|
250648
|
+
},
|
|
250649
|
+
meta: {
|
|
250650
|
+
docs: { description: 'Disallow nested interactive elements' },
|
|
250651
|
+
messages: {
|
|
250652
|
+
[MESSAGE_ID$b]: 'Unexpected interactive element nested inside interactive <{{tag}}>.',
|
|
250653
|
+
},
|
|
250654
|
+
schema: [],
|
|
250655
|
+
type: 'problem',
|
|
250656
|
+
},
|
|
250657
|
+
},
|
|
250658
|
+
});
|
|
250659
|
+
|
|
250660
|
+
function isBoundAttribute(node) {
|
|
250661
|
+
return node instanceof dist$4.TmplAstBoundAttribute;
|
|
250662
|
+
}
|
|
250663
|
+
function getBoundAttributes(container) {
|
|
250664
|
+
return container instanceof dist$4.TmplAstTemplate
|
|
250665
|
+
? [...container.inputs, ...container.templateAttrs.filter(isBoundAttribute)]
|
|
250666
|
+
: container.inputs;
|
|
250667
|
+
}
|
|
250668
|
+
function getContainingBoundAttribute(container, node) {
|
|
250669
|
+
return container
|
|
250670
|
+
? (getBoundAttributes(container).find((attribute) => containsAbsoluteSourceSpan(attribute.value.sourceSpan, node.sourceSpan)) ?? null)
|
|
250671
|
+
: null;
|
|
250672
|
+
}
|
|
250673
|
+
|
|
250674
|
+
function isConditional(node) {
|
|
250675
|
+
return 'condition' in node && 'trueExp' in node && 'falseExp' in node;
|
|
250676
|
+
}
|
|
250677
|
+
|
|
250678
|
+
class TemplateIdentifierCollector extends dist$4.TmplAstRecursiveVisitor {
|
|
250679
|
+
constructor(names) {
|
|
250680
|
+
super();
|
|
250681
|
+
this.names = names;
|
|
250682
|
+
}
|
|
250683
|
+
visitLetDeclaration(node) {
|
|
250684
|
+
this.names.add(node.name);
|
|
250685
|
+
}
|
|
250686
|
+
visitReference(node) {
|
|
250687
|
+
this.names.add(node.name);
|
|
250688
|
+
}
|
|
250689
|
+
visitVariable(node) {
|
|
250690
|
+
this.names.add(node.name);
|
|
250691
|
+
}
|
|
250692
|
+
}
|
|
250693
|
+
function getTemplateNodes(ast) {
|
|
250694
|
+
if (!ast || typeof ast !== 'object' || !('templateNodes' in ast)) {
|
|
250695
|
+
return [];
|
|
250696
|
+
}
|
|
250697
|
+
const { templateNodes } = ast;
|
|
250698
|
+
return Array.isArray(templateNodes) ? templateNodes : [];
|
|
250699
|
+
}
|
|
250700
|
+
function collectTemplateIdentifiers(ast) {
|
|
250701
|
+
const names = new Set();
|
|
250702
|
+
dist$4.tmplAstVisitAll(new TemplateIdentifierCollector(names), [...getTemplateNodes(ast)]);
|
|
250703
|
+
return names;
|
|
250704
|
+
}
|
|
250705
|
+
|
|
250706
|
+
const IDENTIFIER = /^[A-Z_$][\w$]*$/i;
|
|
250707
|
+
const RESERVED_IDENTIFIERS = new Set([
|
|
250708
|
+
'arguments',
|
|
250709
|
+
'await',
|
|
250710
|
+
'break',
|
|
250711
|
+
'case',
|
|
250712
|
+
'catch',
|
|
250713
|
+
'class',
|
|
250714
|
+
'const',
|
|
250715
|
+
'continue',
|
|
250716
|
+
'debugger',
|
|
250717
|
+
'default',
|
|
250718
|
+
'delete',
|
|
250719
|
+
'do',
|
|
250720
|
+
'else',
|
|
250721
|
+
'enum',
|
|
250722
|
+
'eval',
|
|
250723
|
+
'export',
|
|
250724
|
+
'extends',
|
|
250725
|
+
'false',
|
|
250726
|
+
'finally',
|
|
250727
|
+
'for',
|
|
250728
|
+
'function',
|
|
250729
|
+
'if',
|
|
250730
|
+
'implements',
|
|
250731
|
+
'import',
|
|
250732
|
+
'in',
|
|
250733
|
+
'instanceof',
|
|
250734
|
+
'interface',
|
|
250735
|
+
'let',
|
|
250736
|
+
'new',
|
|
250737
|
+
'null',
|
|
250738
|
+
'package',
|
|
250739
|
+
'private',
|
|
250740
|
+
'protected',
|
|
250741
|
+
'public',
|
|
250742
|
+
'return',
|
|
250743
|
+
'static',
|
|
250744
|
+
'super',
|
|
250745
|
+
'switch',
|
|
250746
|
+
'this',
|
|
250747
|
+
'throw',
|
|
250748
|
+
'true',
|
|
250749
|
+
'try',
|
|
250750
|
+
'typeof',
|
|
250751
|
+
'undefined',
|
|
250752
|
+
'var',
|
|
250753
|
+
'void',
|
|
250754
|
+
'while',
|
|
250755
|
+
'with',
|
|
250756
|
+
'yield',
|
|
250757
|
+
]);
|
|
250758
|
+
function isIdentifier(name) {
|
|
250759
|
+
return IDENTIFIER.test(name) && !RESERVED_IDENTIFIERS.has(name);
|
|
250760
|
+
}
|
|
250761
|
+
function getAvailableIdentifier(baseName, unavailableNames) {
|
|
250762
|
+
if (!isIdentifier(baseName)) {
|
|
250763
|
+
throw new Error(`Expected a valid identifier, got "${baseName}"`);
|
|
250764
|
+
}
|
|
250765
|
+
if (!unavailableNames.has(baseName)) {
|
|
250766
|
+
return baseName;
|
|
250767
|
+
}
|
|
250768
|
+
let suffix = 2;
|
|
250769
|
+
let name = `${baseName}${suffix}`;
|
|
250770
|
+
while (unavailableNames.has(name)) {
|
|
250771
|
+
suffix++;
|
|
250772
|
+
name = `${baseName}${suffix}`;
|
|
250773
|
+
}
|
|
250774
|
+
return name;
|
|
250775
|
+
}
|
|
250776
|
+
|
|
250777
|
+
function isSingleLineNode(node) {
|
|
250778
|
+
return node.loc.start.line === node.loc.end.line;
|
|
250779
|
+
}
|
|
250780
|
+
function hasCommentLikeText(text) {
|
|
250781
|
+
return text.includes('//') || text.includes('/*');
|
|
250782
|
+
}
|
|
250783
|
+
function hasBlankLine(text) {
|
|
250784
|
+
let lineBreaks = 0;
|
|
250785
|
+
for (let index = 0; index < text.length; index++) {
|
|
250786
|
+
const char = text[index];
|
|
250787
|
+
if (char === '\n') {
|
|
250788
|
+
lineBreaks++;
|
|
250789
|
+
}
|
|
250790
|
+
else if (char === '\r') {
|
|
250791
|
+
lineBreaks++;
|
|
250792
|
+
if (text[index + 1] === '\n') {
|
|
250793
|
+
index++;
|
|
250794
|
+
}
|
|
250795
|
+
}
|
|
250796
|
+
if (lineBreaks > 1) {
|
|
250797
|
+
return true;
|
|
250798
|
+
}
|
|
250799
|
+
}
|
|
250800
|
+
return false;
|
|
250801
|
+
}
|
|
250802
|
+
function getLineBreak(text) {
|
|
250803
|
+
if (text.includes('\r\n')) {
|
|
250804
|
+
return '\r\n';
|
|
250805
|
+
}
|
|
250806
|
+
return text.includes('\r') ? '\r' : '\n';
|
|
250807
|
+
}
|
|
250808
|
+
function getLeadingIndentation(text) {
|
|
250809
|
+
let index = 0;
|
|
250810
|
+
while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
|
|
250811
|
+
index++;
|
|
250812
|
+
}
|
|
250813
|
+
return text.slice(0, index);
|
|
250814
|
+
}
|
|
250815
|
+
function getIndentAtOffset(text, offset) {
|
|
250816
|
+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
|
|
250817
|
+
const indent = text.slice(lineStart, offset);
|
|
250818
|
+
return indent.trim() === '' ? indent : '';
|
|
250819
|
+
}
|
|
250820
|
+
function getSpacingReplacement(sourceCode, betweenText, nextLine, blankLineCount) {
|
|
250821
|
+
const indentation = getLeadingIndentation(sourceCode.lines[nextLine - 1] ?? '');
|
|
250822
|
+
return `${getLineBreak(betweenText).repeat(blankLineCount + 1)}${indentation}`;
|
|
250823
|
+
}
|
|
250824
|
+
|
|
250825
|
+
const MESSAGE_ID$a = 'noNestedTernaryInTemplate';
|
|
250826
|
+
const MESSAGE = 'Avoid nested ternary expressions in Angular templates. Move the nested branch into an @let declaration.';
|
|
250827
|
+
function createLetFixes(node, baseName, unavailableNames, text) {
|
|
250828
|
+
const name = getAvailableIdentifier(baseName, unavailableNames);
|
|
250829
|
+
unavailableNames.add(name);
|
|
250830
|
+
const nestedLets = [];
|
|
250831
|
+
const render = (child) => {
|
|
250832
|
+
if (isConditional(child)) {
|
|
250833
|
+
const result = createLetFixes(child, baseName, unavailableNames, text);
|
|
250834
|
+
nestedLets.push(...result.lets);
|
|
250835
|
+
return result.reference;
|
|
250836
|
+
}
|
|
250837
|
+
return getAbsoluteSourceSpanText(text, child.sourceSpan);
|
|
250838
|
+
};
|
|
250839
|
+
const condition = render(node.condition);
|
|
250840
|
+
const trueExp = render(node.trueExp);
|
|
250841
|
+
const falseExp = render(node.falseExp);
|
|
250842
|
+
return {
|
|
250843
|
+
lets: [
|
|
250844
|
+
...nestedLets,
|
|
250845
|
+
{
|
|
250846
|
+
expression: `${condition} ? ${trueExp} : ${falseExp}`,
|
|
250847
|
+
name,
|
|
250848
|
+
},
|
|
250849
|
+
],
|
|
250850
|
+
reference: name,
|
|
250851
|
+
};
|
|
250852
|
+
}
|
|
250853
|
+
const rule$A = createRule({
|
|
250854
|
+
name: 'no-nested-ternary-in-template',
|
|
250855
|
+
rule: {
|
|
250856
|
+
create(context) {
|
|
250857
|
+
const { sourceCode } = context;
|
|
250858
|
+
const conditionalStack = [];
|
|
250859
|
+
const containerStack = [];
|
|
250860
|
+
const letNames = collectTemplateIdentifiers(sourceCode.ast);
|
|
250861
|
+
let boundEventDepth = 0;
|
|
250862
|
+
let letDeclarationDepth = 0;
|
|
250863
|
+
function reportNestedConditional(node, options) {
|
|
250864
|
+
const container = containerStack[containerStack.length - 1];
|
|
250865
|
+
const attribute = getContainingBoundAttribute(container, node);
|
|
250866
|
+
const baseName = attribute?.name;
|
|
250867
|
+
const fixable = options.allowFix &&
|
|
250868
|
+
boundEventDepth === 0 &&
|
|
250869
|
+
letDeclarationDepth === 0 &&
|
|
250870
|
+
baseName &&
|
|
250871
|
+
isIdentifier(baseName);
|
|
250872
|
+
context.report({
|
|
250873
|
+
...(fixable
|
|
250874
|
+
? {
|
|
250875
|
+
fix(fixer) {
|
|
250876
|
+
const result = createLetFixes(node, baseName, letNames, sourceCode.text);
|
|
250877
|
+
const insertOffset = container?.startSourceSpan.start.offset;
|
|
250878
|
+
if (insertOffset === undefined) {
|
|
250879
|
+
return null;
|
|
250880
|
+
}
|
|
250881
|
+
const indent = getIndentAtOffset(sourceCode.text, insertOffset);
|
|
250882
|
+
const declarations = result.lets
|
|
250883
|
+
.map(({ expression, name }, index) => `${index === 0 ? '' : indent}@let ${name} = ${expression};`)
|
|
250884
|
+
.join('\n');
|
|
250885
|
+
return [
|
|
250886
|
+
fixer.insertTextBeforeRange([insertOffset, insertOffset], `${declarations}\n${indent}`),
|
|
250887
|
+
fixer.replaceTextRange([node.sourceSpan.start, node.sourceSpan.end], result.reference),
|
|
250888
|
+
];
|
|
250889
|
+
},
|
|
250890
|
+
}
|
|
250891
|
+
: {}),
|
|
250892
|
+
loc: {
|
|
250893
|
+
end: sourceCode.getLocFromIndex(node.sourceSpan.end),
|
|
250894
|
+
start: sourceCode.getLocFromIndex(node.sourceSpan.start),
|
|
250895
|
+
},
|
|
250896
|
+
messageId: MESSAGE_ID$a,
|
|
250897
|
+
});
|
|
250898
|
+
}
|
|
250899
|
+
return {
|
|
250900
|
+
BoundEvent() {
|
|
250901
|
+
boundEventDepth++;
|
|
250902
|
+
},
|
|
250903
|
+
'BoundEvent:exit'() {
|
|
250904
|
+
boundEventDepth--;
|
|
250905
|
+
},
|
|
250906
|
+
Conditional(rawNode) {
|
|
250907
|
+
const node = rawNode;
|
|
250908
|
+
if (conditionalStack.length > 0) {
|
|
250909
|
+
reportNestedConditional(node, {
|
|
250910
|
+
allowFix: conditionalStack.length === 1,
|
|
250911
|
+
});
|
|
250912
|
+
}
|
|
250913
|
+
conditionalStack.push(node);
|
|
250914
|
+
},
|
|
250915
|
+
'Conditional:exit'(rawNode) {
|
|
250916
|
+
const node = rawNode;
|
|
250917
|
+
if (conditionalStack[conditionalStack.length - 1] === node) {
|
|
250918
|
+
conditionalStack.pop();
|
|
250919
|
+
}
|
|
250920
|
+
},
|
|
250921
|
+
Element(rawNode) {
|
|
250922
|
+
containerStack.push(rawNode);
|
|
250923
|
+
},
|
|
250924
|
+
'Element:exit'(rawNode) {
|
|
250925
|
+
if (containerStack[containerStack.length - 1] === rawNode) {
|
|
250926
|
+
containerStack.pop();
|
|
250927
|
+
}
|
|
250928
|
+
},
|
|
250929
|
+
LetDeclaration() {
|
|
250930
|
+
letDeclarationDepth++;
|
|
250931
|
+
},
|
|
250932
|
+
'LetDeclaration:exit'() {
|
|
250933
|
+
letDeclarationDepth--;
|
|
250934
|
+
},
|
|
250935
|
+
Template(rawNode) {
|
|
250936
|
+
containerStack.push(rawNode);
|
|
250937
|
+
},
|
|
250938
|
+
'Template:exit'(rawNode) {
|
|
250939
|
+
if (containerStack[containerStack.length - 1] === rawNode) {
|
|
250940
|
+
containerStack.pop();
|
|
250941
|
+
}
|
|
250942
|
+
},
|
|
250943
|
+
};
|
|
250944
|
+
},
|
|
250945
|
+
meta: {
|
|
250946
|
+
docs: { description: MESSAGE },
|
|
250947
|
+
fixable: 'code',
|
|
250948
|
+
messages: { [MESSAGE_ID$a]: MESSAGE },
|
|
250949
|
+
schema: [],
|
|
250950
|
+
type: 'suggestion',
|
|
250951
|
+
},
|
|
250952
|
+
},
|
|
250953
|
+
});
|
|
250954
|
+
|
|
250525
250955
|
const OBSOLETE_HTML_ATTRS = {
|
|
250526
250956
|
abbr: [
|
|
250527
250957
|
{
|
|
@@ -254048,10 +254478,6 @@ const rule$e = createRule({
|
|
|
254048
254478
|
});
|
|
254049
254479
|
|
|
254050
254480
|
const MESSAGE_ID$5 = 'missingAlt';
|
|
254051
|
-
function hasAlt(node) {
|
|
254052
|
-
return (node.attributes.some((attr) => attr.name === 'alt') ||
|
|
254053
|
-
node.inputs.some((input) => input.name === 'alt' || input.keySpan.details === 'attr.alt'));
|
|
254054
|
-
}
|
|
254055
254481
|
const rule$d = createRule({
|
|
254056
254482
|
name: 'require-img-alt',
|
|
254057
254483
|
rule: {
|
|
@@ -254059,7 +254485,7 @@ const rule$d = createRule({
|
|
|
254059
254485
|
return {
|
|
254060
254486
|
Element(rawNode) {
|
|
254061
254487
|
const node = rawNode;
|
|
254062
|
-
if (node.name !== 'img' ||
|
|
254488
|
+
if (node.name !== 'img' || hasElementAttribute(node, 'alt')) {
|
|
254063
254489
|
return;
|
|
254064
254490
|
}
|
|
254065
254491
|
context.report({
|
|
@@ -254092,16 +254518,14 @@ const rule$c = createRule({
|
|
|
254092
254518
|
if (node.name !== 'html') {
|
|
254093
254519
|
return;
|
|
254094
254520
|
}
|
|
254095
|
-
|
|
254096
|
-
const hasBoundLang = node.inputs.some((input) => input.name === 'lang' ||
|
|
254097
|
-
input.keySpan.details === 'attr.lang');
|
|
254098
|
-
if (!langAttr && !hasBoundLang) {
|
|
254521
|
+
if (!hasElementAttribute(node, 'lang')) {
|
|
254099
254522
|
context.report({
|
|
254100
254523
|
loc: sourceSpanToLoc(node.startSourceSpan),
|
|
254101
254524
|
messageId: MESSAGE_IDS$1.MISSING,
|
|
254102
254525
|
});
|
|
254103
254526
|
return;
|
|
254104
254527
|
}
|
|
254528
|
+
const langAttr = getStaticAttribute(node, 'lang');
|
|
254105
254529
|
if (langAttr?.value.trim().length === 0) {
|
|
254106
254530
|
context.report({
|
|
254107
254531
|
loc: sourceSpanToLoc(langAttr.sourceSpan),
|
|
@@ -254430,49 +254854,6 @@ const rule$9 = createRule({
|
|
|
254430
254854
|
name: 'short-tui-imports',
|
|
254431
254855
|
});
|
|
254432
254856
|
|
|
254433
|
-
function isSingleLineNode(node) {
|
|
254434
|
-
return node.loc.start.line === node.loc.end.line;
|
|
254435
|
-
}
|
|
254436
|
-
function hasCommentLikeText(text) {
|
|
254437
|
-
return text.includes('//') || text.includes('/*');
|
|
254438
|
-
}
|
|
254439
|
-
function hasBlankLine(text) {
|
|
254440
|
-
let lineBreaks = 0;
|
|
254441
|
-
for (let index = 0; index < text.length; index++) {
|
|
254442
|
-
const char = text[index];
|
|
254443
|
-
if (char === '\n') {
|
|
254444
|
-
lineBreaks++;
|
|
254445
|
-
}
|
|
254446
|
-
else if (char === '\r') {
|
|
254447
|
-
lineBreaks++;
|
|
254448
|
-
if (text[index + 1] === '\n') {
|
|
254449
|
-
index++;
|
|
254450
|
-
}
|
|
254451
|
-
}
|
|
254452
|
-
if (lineBreaks > 1) {
|
|
254453
|
-
return true;
|
|
254454
|
-
}
|
|
254455
|
-
}
|
|
254456
|
-
return false;
|
|
254457
|
-
}
|
|
254458
|
-
function getLineBreak(text) {
|
|
254459
|
-
if (text.includes('\r\n')) {
|
|
254460
|
-
return '\r\n';
|
|
254461
|
-
}
|
|
254462
|
-
return text.includes('\r') ? '\r' : '\n';
|
|
254463
|
-
}
|
|
254464
|
-
function getLeadingIndentation(text) {
|
|
254465
|
-
let index = 0;
|
|
254466
|
-
while (index < text.length && (text[index] === ' ' || text[index] === '\t')) {
|
|
254467
|
-
index++;
|
|
254468
|
-
}
|
|
254469
|
-
return text.slice(0, index);
|
|
254470
|
-
}
|
|
254471
|
-
function getSpacingReplacement(sourceCode, betweenText, nextLine, blankLineCount) {
|
|
254472
|
-
const indentation = getLeadingIndentation(sourceCode.lines[nextLine - 1] ?? '');
|
|
254473
|
-
return `${getLineBreak(betweenText).repeat(blankLineCount + 1)}${indentation}`;
|
|
254474
|
-
}
|
|
254475
|
-
|
|
254476
254857
|
function isFieldLikeMember(member) {
|
|
254477
254858
|
return (member.type === dist$3.AST_NODE_TYPES.PropertyDefinition ||
|
|
254478
254859
|
member.type === dist$3.AST_NODE_TYPES.TSAbstractPropertyDefinition);
|
|
@@ -255707,28 +256088,30 @@ const plugin = {
|
|
|
255707
256088
|
},
|
|
255708
256089
|
rules: {
|
|
255709
256090
|
'array-as-const': rule$5,
|
|
255710
|
-
'attrs-newline': rule$
|
|
256091
|
+
'attrs-newline': rule$V,
|
|
255711
256092
|
'class-property-naming': rule$4,
|
|
255712
|
-
'decorator-key-sort': rule$
|
|
255713
|
-
'element-newline': rule$
|
|
256093
|
+
'decorator-key-sort': rule$U,
|
|
256094
|
+
'element-newline': rule$T,
|
|
255714
256095
|
'flat-exports': rule$3,
|
|
255715
|
-
'host-attributes-sort': rule$
|
|
255716
|
-
'html-logical-properties': rule$
|
|
255717
|
-
'import-integrity': rule$
|
|
255718
|
-
'injection-token-description': rule$
|
|
255719
|
-
'no-commonjs-import-patterns': rule$
|
|
255720
|
-
'no-deep-imports': rule$
|
|
255721
|
-
'no-deep-imports-to-indexed-packages': rule$
|
|
255722
|
-
'no-duplicate-attrs': rule$
|
|
255723
|
-
'no-duplicate-id': rule$
|
|
255724
|
-
'no-duplicate-in-head': rule$
|
|
255725
|
-
'no-empty-style-metadata': rule$
|
|
255726
|
-
'no-fully-untracked-effect': rule$
|
|
255727
|
-
'no-href-with-router-link': rule$
|
|
255728
|
-
'no-implicit-public': rule$
|
|
255729
|
-
'no-import-assertions': rule$
|
|
255730
|
-
'no-infinite-loop': rule$
|
|
255731
|
-
'no-legacy-peer-deps': rule$
|
|
256096
|
+
'host-attributes-sort': rule$S,
|
|
256097
|
+
'html-logical-properties': rule$R,
|
|
256098
|
+
'import-integrity': rule$Q,
|
|
256099
|
+
'injection-token-description': rule$P,
|
|
256100
|
+
'no-commonjs-import-patterns': rule$O,
|
|
256101
|
+
'no-deep-imports': rule$N,
|
|
256102
|
+
'no-deep-imports-to-indexed-packages': rule$M,
|
|
256103
|
+
'no-duplicate-attrs': rule$L,
|
|
256104
|
+
'no-duplicate-id': rule$K,
|
|
256105
|
+
'no-duplicate-in-head': rule$J,
|
|
256106
|
+
'no-empty-style-metadata': rule$I,
|
|
256107
|
+
'no-fully-untracked-effect': rule$H,
|
|
256108
|
+
'no-href-with-router-link': rule$G,
|
|
256109
|
+
'no-implicit-public': rule$F,
|
|
256110
|
+
'no-import-assertions': rule$E,
|
|
256111
|
+
'no-infinite-loop': rule$D,
|
|
256112
|
+
'no-legacy-peer-deps': rule$C,
|
|
256113
|
+
'no-nested-interactive': rule$B,
|
|
256114
|
+
'no-nested-ternary-in-template': rule$A,
|
|
255732
256115
|
'no-obsolete-attrs': rule$z,
|
|
255733
256116
|
'no-obsolete-tags': rule$y,
|
|
255734
256117
|
'no-playwright-empty-fill': rule$x,
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ui/eslint-plugin-experience-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.515.0",
|
|
4
4
|
"description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
|
|
5
|
+
"homepage": "https://github.com/taiga-family/toolkit#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/taiga-family/toolkit/issues"
|
|
8
|
+
},
|
|
5
9
|
"repository": {
|
|
6
10
|
"type": "git",
|
|
7
11
|
"url": "https://github.com/taiga-family/toolkit.git"
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { type ParseSourceSpan, type TmplAstBoundAttribute, TmplAstBoundEvent, type TmplAstElement, type TmplAstReference, type TmplAstTextAttribute } from '@angular-eslint/bundled-angular-compiler';
|
|
2
2
|
export type ElementAttributeLike = TmplAstBoundAttribute | TmplAstBoundEvent | TmplAstReference | TmplAstTextAttribute;
|
|
3
|
+
export type ElementAttributeName = string | readonly string[];
|
|
3
4
|
export declare function getAttributeValueSpan(attr: ElementAttributeLike): ParseSourceSpan | undefined;
|
|
5
|
+
export declare function getStaticAttribute(element: TmplAstElement, name: string): TmplAstTextAttribute | undefined;
|
|
6
|
+
export declare function getStaticAttributeValue(element: TmplAstElement, name: string): string | undefined;
|
|
7
|
+
export declare function hasInputBinding(element: TmplAstElement, name: ElementAttributeName): boolean;
|
|
8
|
+
export declare function hasAttributeBinding(element: TmplAstElement, name: ElementAttributeName): boolean;
|
|
9
|
+
export declare function hasElementAttribute(element: TmplAstElement, name: ElementAttributeName): boolean;
|
|
10
|
+
export declare function hasOutputBinding(element: TmplAstElement, name?: ElementAttributeName): boolean;
|
|
4
11
|
export declare function getElementAttributeLikes(element: TmplAstElement): ElementAttributeLike[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { type ParseSourceSpan } from '@angular-eslint/bundled-angular-compiler';
|
|
1
|
+
import { type AbsoluteSourceSpan, type ParseSourceSpan } from '@angular-eslint/bundled-angular-compiler';
|
|
2
2
|
import { type SourceLocation } from 'estree';
|
|
3
3
|
export declare function sourceSpanToLoc(span: ParseSourceSpan): SourceLocation;
|
|
4
|
+
export declare function containsAbsoluteSourceSpan(container: AbsoluteSourceSpan, child: AbsoluteSourceSpan): boolean;
|
|
5
|
+
export declare function getAbsoluteSourceSpanText(text: string, span: AbsoluteSourceSpan): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AST, TmplAstBoundAttribute, type TmplAstElement, TmplAstTemplate } from '@angular-eslint/bundled-angular-compiler';
|
|
2
|
+
export type TemplateAttributeContainer = TmplAstElement | TmplAstTemplate;
|
|
3
|
+
export declare function isBoundAttribute(node: unknown): node is TmplAstBoundAttribute;
|
|
4
|
+
export declare function getBoundAttributes(container: TemplateAttributeContainer): readonly TmplAstBoundAttribute[];
|
|
5
|
+
export declare function getContainingBoundAttribute(container: TemplateAttributeContainer | undefined, node: AST): TmplAstBoundAttribute | null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type AST, type ASTWithSource, type Conditional } from '@angular-eslint/bundled-angular-compiler';
|
|
2
|
+
export declare function isAstWithSource(node: AST): node is ASTWithSource;
|
|
3
|
+
export declare function unwrapAstWithSource(node: AST): AST;
|
|
4
|
+
export declare function isConditional(node: AST): node is Conditional;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type TmplAstLetDeclaration, type TmplAstNode, TmplAstRecursiveVisitor, type TmplAstReference, type TmplAstVariable } from '@angular-eslint/bundled-angular-compiler';
|
|
2
|
+
export declare class TemplateIdentifierCollector extends TmplAstRecursiveVisitor {
|
|
3
|
+
private readonly names;
|
|
4
|
+
constructor(names: Set<string>);
|
|
5
|
+
visitLetDeclaration(node: TmplAstLetDeclaration): void;
|
|
6
|
+
visitReference(node: TmplAstReference): void;
|
|
7
|
+
visitVariable(node: TmplAstVariable): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function getTemplateNodes(ast: unknown): readonly TmplAstNode[];
|
|
10
|
+
export declare function collectTemplateIdentifiers(ast: unknown): Set<string>;
|
|
@@ -4,4 +4,5 @@ export declare function hasCommentLikeText(text: string): boolean;
|
|
|
4
4
|
export declare function hasBlankLine(text: string): boolean;
|
|
5
5
|
export declare function getLineBreak(text: string): string;
|
|
6
6
|
export declare function getLeadingIndentation(text: string): string;
|
|
7
|
+
export declare function getIndentAtOffset(text: string, offset: number): string;
|
|
7
8
|
export declare function getSpacingReplacement(sourceCode: Readonly<TSESLint.SourceCode>, betweenText: string, nextLine: number, blankLineCount: number): string;
|