@arcgis/eslint-config 4.33.0-next.65 → 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.
@@ -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,4 +1,4 @@
1
- const version = "4.33.0-next.65";
1
+ const version = "4.33.0-next.66";
2
2
  function makeEslintPlugin(pluginName, rules) {
3
3
  const config = {
4
4
  rules: Object.fromEntries(
@@ -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-mahtcMBO.js";
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$h = `To use Lumina's JSX, you need to ${importDeclaration}`;
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$h,
102
+ description: description$i,
103
103
  defaultLevel: "error"
104
104
  },
105
105
  messages: {
106
- addMissingJsxImport: description$h
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$g = "Auto add { type: Boolean } or { type: Number } where necessary";
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$g,
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$f = `Lumina component must be declared in a TSX file with a matching folder name located inside of src/components folder.`;
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$f,
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$e = `Enforce consistent event naming.`;
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$e,
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$d = `Enforce that @property(), @method() and createEvent() members are used in the correct context.`;
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$d,
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,2 +1,4 @@
1
1
  import { default as ts } from 'typescript';
2
2
  export declare function isLuminaJsxType(type: ts.Type): boolean;
3
+ export declare const hasTypeFlag: (type: ts.Type, flag: ts.TypeFlags) => boolean;
4
+ export declare const literalTypeFlag: number;
@@ -1,4 +1,4 @@
1
- import { m as makeEslintPlugin } from "../../makePlugin-mahtcMBO.js";
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.65",
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.65",
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",