@arcgis/eslint-config 4.33.0-next.64 → 4.33.0-next.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.js +3 -1
- package/dist/{makePlugin-B2kjgMoQ.js → makePlugin-DfmxVuno.js} +1 -1
- package/dist/plugins/lumina/index.js +151 -39
- package/dist/plugins/lumina/rules/explicit-setter-type.d.ts +18 -0
- package/dist/plugins/lumina/utils/checker.d.ts +2 -0
- package/dist/plugins/webgis/index.js +1 -1
- package/package.json +2 -2
package/dist/config/index.js
CHANGED
|
@@ -605,7 +605,9 @@ const index = [
|
|
|
605
605
|
// This is error by default. Turning into warning because it falsely
|
|
606
606
|
// reports when component tries to emit an event that is marked as
|
|
607
607
|
// deprecated.
|
|
608
|
-
"@typescript-eslint/no-deprecated": "warn"
|
|
608
|
+
"@typescript-eslint/no-deprecated": "warn",
|
|
609
|
+
// For can be useful in support packages and hot paths
|
|
610
|
+
"@typescript-eslint/prefer-for-of": "off"
|
|
609
611
|
//#endregion
|
|
610
612
|
// TODO: go over https://typescript-eslint.io/linting/troubleshooting/performance-troubleshooting
|
|
611
613
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ESLintUtils, AST_NODE_TYPES, AST_TOKEN_TYPES } from "@typescript-eslint/utils";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
-
import { m as makeEslintPlugin } from "../../makePlugin-
|
|
3
|
+
import { m as makeEslintPlugin } from "../../makePlugin-DfmxVuno.js";
|
|
4
4
|
import { camelToKebab } from "@arcgis/components-utils";
|
|
5
5
|
const createRule = ESLintUtils.RuleCreator(
|
|
6
6
|
(rule) => `https://devtopia.esri.com/WebGIS/arcgis-web-components/tree/main/packages/support-packages/eslint-config/src/plugins/lumina/rules/${rule}.ts`
|
|
@@ -94,16 +94,16 @@ function isBindThisCallee(callee) {
|
|
|
94
94
|
callee.object.object.type === AST_NODE_TYPES.ThisExpression;
|
|
95
95
|
}
|
|
96
96
|
const importDeclaration = `import { ${luminaJsxExportName} } from "${luminaEntrypointName}";`;
|
|
97
|
-
const description$
|
|
97
|
+
const description$i = `To use Lumina's JSX, you need to ${importDeclaration}`;
|
|
98
98
|
const addMissingJsxImport = createRule({
|
|
99
99
|
name: "add-missing-jsx-import",
|
|
100
100
|
meta: {
|
|
101
101
|
docs: {
|
|
102
|
-
description: description$
|
|
102
|
+
description: description$i,
|
|
103
103
|
defaultLevel: "error"
|
|
104
104
|
},
|
|
105
105
|
messages: {
|
|
106
|
-
addMissingJsxImport: description$
|
|
106
|
+
addMissingJsxImport: description$i
|
|
107
107
|
},
|
|
108
108
|
type: "problem",
|
|
109
109
|
schema: [],
|
|
@@ -160,12 +160,12 @@ const addMissingJsxImport = createRule({
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
});
|
|
163
|
-
const description$
|
|
163
|
+
const description$h = "Auto add { type: Boolean } or { type: Number } where necessary";
|
|
164
164
|
const autoAddType = createRule({
|
|
165
165
|
name: "auto-add-type",
|
|
166
166
|
meta: {
|
|
167
167
|
docs: {
|
|
168
|
-
description: description$
|
|
168
|
+
description: description$h,
|
|
169
169
|
defaultLevel: "warn"
|
|
170
170
|
},
|
|
171
171
|
messages: {
|
|
@@ -326,12 +326,12 @@ function inferTrivialType(member) {
|
|
|
326
326
|
return "Other";
|
|
327
327
|
}
|
|
328
328
|
const builtInConverterTypes = /* @__PURE__ */ new Set(["Number", "Boolean", "Array", "Object"]);
|
|
329
|
-
const description$
|
|
329
|
+
const description$g = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
|
|
330
330
|
const componentPlacementRules = createRule({
|
|
331
331
|
name: "component-placement-rules",
|
|
332
332
|
meta: {
|
|
333
333
|
docs: {
|
|
334
|
-
description: description$
|
|
334
|
+
description: description$g,
|
|
335
335
|
defaultLevel: "error"
|
|
336
336
|
},
|
|
337
337
|
messages: {
|
|
@@ -370,7 +370,7 @@ const componentPlacementRules = createRule({
|
|
|
370
370
|
};
|
|
371
371
|
}
|
|
372
372
|
});
|
|
373
|
-
const description$
|
|
373
|
+
const description$f = `Enforce consistent event naming.`;
|
|
374
374
|
const defaultOptions$1 = [
|
|
375
375
|
{
|
|
376
376
|
eventNamespaces: ["arcgis"],
|
|
@@ -381,7 +381,7 @@ const consistentEventNaming = createRule({
|
|
|
381
381
|
name: "consistent-event-naming",
|
|
382
382
|
meta: {
|
|
383
383
|
docs: {
|
|
384
|
-
description: description$
|
|
384
|
+
description: description$f,
|
|
385
385
|
defaultLevel: "warn"
|
|
386
386
|
},
|
|
387
387
|
messages: {
|
|
@@ -482,12 +482,12 @@ Discussion: https://devtopia.esri.com/WebGIS/arcgis-web-components/discussions/3
|
|
|
482
482
|
}
|
|
483
483
|
});
|
|
484
484
|
const capitalAfterLower = /(?<=[a-z\d])[A-Z]/u;
|
|
485
|
-
const description$
|
|
485
|
+
const description$e = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
|
|
486
486
|
const decoratorsContext = createRule({
|
|
487
487
|
name: "decorators-context",
|
|
488
488
|
meta: {
|
|
489
489
|
docs: {
|
|
490
|
-
description: description$
|
|
490
|
+
description: description$e,
|
|
491
491
|
defaultLevel: "error"
|
|
492
492
|
},
|
|
493
493
|
messages: {
|
|
@@ -552,6 +552,144 @@ If you wish to hide this member from public documentation, use @private or @prot
|
|
|
552
552
|
};
|
|
553
553
|
}
|
|
554
554
|
});
|
|
555
|
+
const litTemplateResult = "TemplateResult";
|
|
556
|
+
const explicitJsxTypeName = "JsxNode";
|
|
557
|
+
const implicitJsxTypeName = "Element";
|
|
558
|
+
const implicitJsxParentName = "LuminaJsx";
|
|
559
|
+
function isLuminaJsxType(type) {
|
|
560
|
+
for (const declaration of type.aliasSymbol?.declarations ?? []) {
|
|
561
|
+
if (!ts.isTypeAliasDeclaration(declaration)) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const typeName = declaration.name.escapedText;
|
|
565
|
+
if (typeName === litTemplateResult || typeName === explicitJsxTypeName) {
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
if (typeName !== implicitJsxTypeName) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const grandParent = declaration.parent?.parent;
|
|
572
|
+
if (!ts.isModuleDeclaration(grandParent)) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const symbol = grandParent.symbol;
|
|
576
|
+
if (symbol?.escapedName === implicitJsxParentName) {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
const hasTypeFlag = (type, flag) => type.flags & flag ? true : (type.flags & ts.TypeFlags.Union) !== 0 && type.types.some((t) => hasTypeFlag(t, flag));
|
|
583
|
+
const literalTypeFlag = ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike;
|
|
584
|
+
const description$d = `Need to add explicit type annotation: {{ setterType }}
|
|
585
|
+
|
|
586
|
+
Explanation:
|
|
587
|
+
Lumina automatically creates an attribute for a property if property type includes a literal type (string|number|boolean).
|
|
588
|
+
For bound properties, the property type by default comes from the bound getter type.
|
|
589
|
+
The property you are trying to bind includes a literal in the setter type, but not the getter type.
|
|
590
|
+
An explicit type annotation is required to ensure the property has an attribute created.`;
|
|
591
|
+
const explicitSetterType = createRule({
|
|
592
|
+
name: "explicit-setter-type",
|
|
593
|
+
meta: {
|
|
594
|
+
docs: {
|
|
595
|
+
description: description$d,
|
|
596
|
+
defaultLevel: "error"
|
|
597
|
+
},
|
|
598
|
+
messages: {
|
|
599
|
+
explicitSetterType: description$d,
|
|
600
|
+
addExplicitSetterType: `Add {{ setterType }} type annotation`
|
|
601
|
+
},
|
|
602
|
+
type: "problem",
|
|
603
|
+
schema: [],
|
|
604
|
+
fixable: void 0,
|
|
605
|
+
hasSuggestions: true
|
|
606
|
+
},
|
|
607
|
+
defaultOptions: [],
|
|
608
|
+
create(context) {
|
|
609
|
+
const services = ESLintUtils.getParserServices(context);
|
|
610
|
+
let isUsingLumina = false;
|
|
611
|
+
return {
|
|
612
|
+
ImportDeclaration(node) {
|
|
613
|
+
if (node.source.value === luminaEntrypointName) {
|
|
614
|
+
isUsingLumina = true;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
ClassDeclaration(node) {
|
|
618
|
+
if (!isUsingLumina) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const accessorProperties = /* @__PURE__ */ new Set();
|
|
622
|
+
node.body.body.forEach((member) => {
|
|
623
|
+
if (member.type !== AST_NODE_TYPES.PropertyDefinition) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (member.key.type !== AST_NODE_TYPES.Identifier) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const keyName = member.key.name;
|
|
630
|
+
const isGenericControllerLike = member.value?.type === AST_NODE_TYPES.CallExpression && member.value.callee.type === AST_NODE_TYPES.Identifier && member.value.callee.name.startsWith("use") && member.value.arguments.at(0)?.type === AST_NODE_TYPES.ThisExpression;
|
|
631
|
+
if (isGenericControllerLike) {
|
|
632
|
+
accessorProperties.add(keyName);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const isBoundLike = member.value?.type === AST_NODE_TYPES.MemberExpression && member.value.object.type === AST_NODE_TYPES.MemberExpression && member.value.object.object.type === AST_NODE_TYPES.ThisExpression && member.value.object.property.type === AST_NODE_TYPES.Identifier && accessorProperties.has(member.value.object.property.name);
|
|
636
|
+
if (!isBoundLike) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
accessorProperties.add(keyName);
|
|
640
|
+
const hasPropertyDecorator = member.decorators?.some(
|
|
641
|
+
(decorator) => decorator.expression.type === AST_NODE_TYPES.CallExpression && decorator.expression.callee.type === AST_NODE_TYPES.Identifier && decorator.expression.callee.name === "property"
|
|
642
|
+
);
|
|
643
|
+
if (!hasPropertyDecorator) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (member.typeAnnotation !== void 0) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const symbol = services.getSymbolAtLocation(member.value);
|
|
650
|
+
const setter = symbol?.declarations?.find(ts.isSetAccessor);
|
|
651
|
+
const getter = symbol?.declarations?.find(ts.isGetAccessor);
|
|
652
|
+
if (setter === void 0 || getter === void 0) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const setterTypeNode = setter.parameters[0].type;
|
|
656
|
+
const getterTypeNode = getter.type;
|
|
657
|
+
if (setterTypeNode === void 0 || getterTypeNode === void 0) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const checker = services.program.getTypeChecker();
|
|
661
|
+
const setterType = checker.getTypeAtLocation(setterTypeNode);
|
|
662
|
+
const getterType = checker.getTypeAtLocation(getterTypeNode);
|
|
663
|
+
const setterIncludesLiteral = hasTypeFlag(setterType, literalTypeFlag);
|
|
664
|
+
const getterIncludesLiteral = hasTypeFlag(getterType, literalTypeFlag);
|
|
665
|
+
if (getterIncludesLiteral || !setterIncludesLiteral) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const setterTypeString = setterTypeNode.getText();
|
|
669
|
+
context.report({
|
|
670
|
+
node: member.key,
|
|
671
|
+
messageId: "explicitSetterType",
|
|
672
|
+
data: {
|
|
673
|
+
setterType: setterTypeString
|
|
674
|
+
},
|
|
675
|
+
// Not an auto fix because you may need to add imports too
|
|
676
|
+
suggest: [
|
|
677
|
+
{
|
|
678
|
+
messageId: "addExplicitSetterType",
|
|
679
|
+
data: {
|
|
680
|
+
setterType: setterTypeString
|
|
681
|
+
},
|
|
682
|
+
fix(fixer) {
|
|
683
|
+
return fixer.insertTextAfter(member.key, `: ${setterTypeString}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
});
|
|
555
693
|
const ordering = [
|
|
556
694
|
/*
|
|
557
695
|
* Putting static before instance members is a common OOP convention.
|
|
@@ -1343,33 +1481,6 @@ const noPropertyNameStartWithOn = createRule({
|
|
|
1343
1481
|
};
|
|
1344
1482
|
}
|
|
1345
1483
|
});
|
|
1346
|
-
const litTemplateResult = "TemplateResult";
|
|
1347
|
-
const explicitJsxTypeName = "JsxNode";
|
|
1348
|
-
const implicitJsxTypeName = "Element";
|
|
1349
|
-
const implicitJsxParentName = "LuminaJsx";
|
|
1350
|
-
function isLuminaJsxType(type) {
|
|
1351
|
-
for (const declaration of type.aliasSymbol?.declarations ?? []) {
|
|
1352
|
-
if (!ts.isTypeAliasDeclaration(declaration)) {
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
|
-
const typeName = declaration.name.escapedText;
|
|
1356
|
-
if (typeName === litTemplateResult || typeName === explicitJsxTypeName) {
|
|
1357
|
-
return true;
|
|
1358
|
-
}
|
|
1359
|
-
if (typeName !== implicitJsxTypeName) {
|
|
1360
|
-
continue;
|
|
1361
|
-
}
|
|
1362
|
-
const grandParent = declaration.parent?.parent;
|
|
1363
|
-
if (!ts.isModuleDeclaration(grandParent)) {
|
|
1364
|
-
continue;
|
|
1365
|
-
}
|
|
1366
|
-
const symbol = grandParent.symbol;
|
|
1367
|
-
if (symbol?.escapedName === implicitJsxParentName) {
|
|
1368
|
-
return true;
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
return false;
|
|
1372
|
-
}
|
|
1373
1484
|
const baseDescription = `Avoid accidentally rendering "false" to the screen (Lit stringifies booleans, rather than drop them like React/Stencil).`;
|
|
1374
1485
|
const description$4 = `${baseDescription}
|
|
1375
1486
|
|
|
@@ -1837,6 +1948,7 @@ const luminaPlugin = makeEslintPlugin("lumina", {
|
|
|
1837
1948
|
"component-placement-rules": componentPlacementRules,
|
|
1838
1949
|
"consistent-event-naming": consistentEventNaming,
|
|
1839
1950
|
"decorators-context": decoratorsContext,
|
|
1951
|
+
"explicit-setter-type": explicitSetterType,
|
|
1840
1952
|
"member-ordering": memberOrdering,
|
|
1841
1953
|
"no-ignore-jsdoc-tag": noIgnoreJsDocTag,
|
|
1842
1954
|
"no-incorrect-dynamic-tag-name": noIncorrectDynamicTagName,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
/**
|
|
3
|
+
* @remarks
|
|
4
|
+
* It is easier to to do this in an ESLint rule rather than at build-time.
|
|
5
|
+
* Reason:
|
|
6
|
+
* - TypeScript does not have a syntax for retrieving the setter type
|
|
7
|
+
* (https://github.com/microsoft/TypeScript/issues/60162)
|
|
8
|
+
* - While we could copy the setter type from bound setter at build-time,
|
|
9
|
+
* copying types between files is tricky because of need for adding correct
|
|
10
|
+
* imports for anything referenced in that type and not already included in
|
|
11
|
+
* current file. Doing so in automated way is quite tricky, slow and
|
|
12
|
+
* error-prone.
|
|
13
|
+
*
|
|
14
|
+
* Instead, this rule will require developer to explicitly provide the setter
|
|
15
|
+
* type. Then we can use it as is in the .d.ts file. The getter type can be
|
|
16
|
+
* retrieved easily via `typeof` operator.
|
|
17
|
+
*/
|
|
18
|
+
export declare const explicitSetterType: ESLintUtils.RuleModule<"explicitSetterType" | "addExplicitSetterType", [], import('../../utils/makePlugin').CommonDocs, ESLintUtils.RuleListener>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as makeEslintPlugin } from "../../makePlugin-
|
|
1
|
+
import { m as makeEslintPlugin } from "../../makePlugin-DfmxVuno.js";
|
|
2
2
|
import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
3
3
|
import { resolve } from "node:path/posix";
|
|
4
4
|
const isTestFile = (filePath) => filePath.includes("/test") || filePath.includes(".test") || filePath.includes(".spec") || filePath.includes("e2e") || filePath.includes("__") || filePath.includes("/.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcgis/eslint-config",
|
|
3
|
-
"version": "4.33.0-next.
|
|
3
|
+
"version": "4.33.0-next.66",
|
|
4
4
|
"description": "ESLint configuration for arcgis-web-components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@arcgis/components-utils": "4.33.0-next.
|
|
22
|
+
"@arcgis/components-utils": "4.33.0-next.66",
|
|
23
23
|
"@eslint/js": "^9.17.0",
|
|
24
24
|
"@types/confusing-browser-globals": "^1.0.3",
|
|
25
25
|
"confusing-browser-globals": "^1.0.11",
|