@checkdigit/eslint-plugin 7.7.0-PR.107-46e4 → 7.7.0-PR.107-e11e

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.
@@ -1,9 +1,11 @@
1
1
  // src/require-fixed-services-import.ts
2
- import { ESLintUtils } from "@typescript-eslint/utils";
2
+ import { strict as assert } from "node:assert";
3
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
3
4
  import getDocumentationUrl from "./get-documentation-url.mjs";
4
5
  var ruleId = "require-fixed-services-import";
5
6
  var createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
6
- var SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
7
+ var SERVICE_TYPINGS_IMPORT_PATH = /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
8
+ var SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION = /(?<path>\.\.\/)+services\/(?<service>\w+)\/(?<version>v\d+)(?<index>\/index(?:\.ts)?)?/u;
7
9
  var rule = createRule({
8
10
  name: ruleId,
9
11
  meta: {
@@ -12,28 +14,65 @@ var rule = createRule({
12
14
  description: 'Require fixed "from" with service typing imports from "src/services".'
13
15
  },
14
16
  messages: {
15
- updateServicesImportFrom: 'Update service typing imports to be from the fixed "src/services" path.'
17
+ updateServicesImportSpecifier: "Update service typing import specifiers to be from the corresponding service version namespace.",
18
+ updateServicesImportSource: 'Update service typing imports to be from the fixed "src/services" path.',
19
+ renameServiceTypeReference: "Rename service type reference using the corresponding service version namespace."
16
20
  },
17
21
  fixable: "code",
18
22
  schema: []
19
23
  },
20
24
  defaultOptions: [],
21
25
  create(context) {
26
+ const importedServiceTypeMapping = /* @__PURE__ */ new Map();
22
27
  return {
23
- ImportDeclaration(node) {
24
- const moduleName = node.source.value;
25
- if (SERVICE_TYPINGS_IMPORT_PATH_PREFIX.test(moduleName)) {
28
+ ImportDeclaration(importDeclaration) {
29
+ const moduleName = importDeclaration.source.value;
30
+ if (SERVICE_TYPINGS_IMPORT_PATH.test(moduleName)) {
31
+ const match = SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION.exec(moduleName);
32
+ if (match?.groups) {
33
+ const { service, version } = match.groups;
34
+ assert.ok(service !== void 0 && version !== void 0);
35
+ for (const specifier of importDeclaration.specifiers) {
36
+ if (specifier.type === AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES.Identifier) {
37
+ importedServiceTypeMapping.set(specifier.local.name, `${service}.${specifier.imported.name}`);
38
+ }
39
+ }
40
+ const rangeStart = importDeclaration.specifiers[0]?.range[0];
41
+ assert.ok(rangeStart !== void 0);
42
+ const rangeEnd = importDeclaration.specifiers.at(-1)?.range[1];
43
+ assert.ok(rangeEnd !== void 0);
44
+ context.report({
45
+ messageId: "updateServicesImportSpecifier",
46
+ node: importDeclaration.source,
47
+ *fix(fixer) {
48
+ yield fixer.replaceTextRange([rangeStart, rangeEnd], `${service}V${version.slice(1)} as ${service}`);
49
+ }
50
+ });
51
+ }
26
52
  context.report({
27
- messageId: "updateServicesImportFrom",
28
- node: node.source,
53
+ messageId: "updateServicesImportSource",
54
+ node: importDeclaration.source,
29
55
  *fix(fixer) {
30
56
  yield fixer.replaceText(
31
- node.source,
32
- `'${moduleName.slice(0, moduleName.indexOf("../services") + "../services".length)}'`
57
+ importDeclaration.source,
58
+ `'${moduleName.slice(0, moduleName.indexOf("../services"))}../services/index.ts'`
33
59
  );
34
60
  }
35
61
  });
36
62
  }
63
+ },
64
+ TSTypeReference(typeReference) {
65
+ if (typeReference.typeName.type === AST_NODE_TYPES.Identifier && importedServiceTypeMapping.has(typeReference.typeName.name)) {
66
+ const renamedTypeName = importedServiceTypeMapping.get(typeReference.typeName.name);
67
+ assert.ok(renamedTypeName !== void 0);
68
+ context.report({
69
+ messageId: "renameServiceTypeReference",
70
+ node: typeReference.typeName,
71
+ fix(fixer) {
72
+ return fixer.replaceText(typeReference.typeName, renamedTypeName);
73
+ }
74
+ });
75
+ }
37
76
  }
38
77
  };
39
78
  }
@@ -43,4 +82,4 @@ export {
43
82
  require_fixed_services_import_default as default,
44
83
  ruleId
45
84
  };
46
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JlcXVpcmUtZml4ZWQtc2VydmljZXMtaW1wb3J0LnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsbUJBQW1CO0FBRTVCLE9BQU8seUJBQXlCO0FBRXpCLElBQU0sU0FBUztBQUV0QixJQUFNLGFBQWEsWUFBWSxZQUFZLENBQUMsU0FBUyxvQkFBb0IsSUFBSSxDQUFDO0FBQzlFLElBQU0scUNBQXFDO0FBRTNDLElBQU0sT0FBMkQsV0FBVztBQUFBLEVBQzFFLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUiwwQkFBMEI7QUFBQSxJQUM1QjtBQUFBLElBQ0EsU0FBUztBQUFBLElBQ1QsUUFBUSxDQUFDO0FBQUEsRUFDWDtBQUFBLEVBQ0EsZ0JBQWdCLENBQUM7QUFBQSxFQUNqQixPQUFPLFNBQVM7QUFDZCxXQUFPO0FBQUEsTUFDTCxrQkFBa0IsTUFBTTtBQUN0QixjQUFNLGFBQWEsS0FBSyxPQUFPO0FBQy9CLFlBQUksbUNBQW1DLEtBQUssVUFBVSxHQUFHO0FBQ3ZELGtCQUFRLE9BQU87QUFBQSxZQUNiLFdBQVc7QUFBQSxZQUNYLE1BQU0sS0FBSztBQUFBLFlBQ1gsQ0FBQyxJQUFJLE9BQU87QUFDVixvQkFBTSxNQUFNO0FBQUEsZ0JBQ1YsS0FBSztBQUFBLGdCQUNMLElBQUksV0FBVyxNQUFNLEdBQUcsV0FBVyxRQUFRLGFBQWEsSUFBSSxjQUFjLE1BQU0sQ0FBQztBQUFBLGNBQ25GO0FBQUEsWUFDRjtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0g7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDO0FBRUQsSUFBTyx3Q0FBUTsiLAogICJuYW1lcyI6IFtdCn0K
85
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JlcXVpcmUtZml4ZWQtc2VydmljZXMtaW1wb3J0LnRzIl0sCiAgIm1hcHBpbmdzIjogIjtBQVFBLFNBQVMsVUFBVSxjQUFjO0FBRWpDLFNBQVMsZ0JBQWdCLG1CQUE2QjtBQUV0RCxPQUFPLHlCQUF5QjtBQUV6QixJQUFNLFNBQVM7QUFFdEIsSUFBTSxhQUFhLFlBQVksWUFBWSxDQUFDLFNBQVMsb0JBQW9CLElBQUksQ0FBQztBQUM5RSxJQUFNLDhCQUE4QjtBQUNwQyxJQUFNLDJDQUNKO0FBRUYsSUFBTSxPQUVGLFdBQVc7QUFBQSxFQUNiLE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxVQUFVO0FBQUEsTUFDUiwrQkFDRTtBQUFBLE1BQ0YsNEJBQTRCO0FBQUEsTUFDNUIsNEJBQTRCO0FBQUEsSUFDOUI7QUFBQSxJQUNBLFNBQVM7QUFBQSxJQUNULFFBQVEsQ0FBQztBQUFBLEVBQ1g7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSw2QkFBNkIsb0JBQUksSUFBb0I7QUFFM0QsV0FBTztBQUFBLE1BQ0wsa0JBQWtCLG1CQUFtQjtBQUNuQyxjQUFNLGFBQWEsa0JBQWtCLE9BQU87QUFDNUMsWUFBSSw0QkFBNEIsS0FBSyxVQUFVLEdBQUc7QUFDaEQsZ0JBQU0sUUFBUSx5Q0FBeUMsS0FBSyxVQUFVO0FBQ3RFLGNBQUksT0FBTyxRQUFRO0FBRWpCLGtCQUFNLEVBQUUsU0FBUyxRQUFRLElBQUksTUFBTTtBQUNuQyxtQkFBTyxHQUFHLFlBQVksVUFBYSxZQUFZLE1BQVM7QUFHeEQsdUJBQVcsYUFBYSxrQkFBa0IsWUFBWTtBQUNwRCxrQkFDRSxVQUFVLFNBQVMsZUFBZSxtQkFDbEMsVUFBVSxTQUFTLFNBQVMsZUFBZSxZQUMzQztBQUNBLDJDQUEyQixJQUFJLFVBQVUsTUFBTSxNQUFNLEdBQUcsT0FBTyxJQUFJLFVBQVUsU0FBUyxJQUFJLEVBQUU7QUFBQSxjQUM5RjtBQUFBLFlBQ0Y7QUFFQSxrQkFBTSxhQUFhLGtCQUFrQixXQUFXLENBQUMsR0FBRyxNQUFNLENBQUM7QUFDM0QsbUJBQU8sR0FBRyxlQUFlLE1BQVM7QUFDbEMsa0JBQU0sV0FBVyxrQkFBa0IsV0FBVyxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUM7QUFDN0QsbUJBQU8sR0FBRyxhQUFhLE1BQVM7QUFFaEMsb0JBQVEsT0FBTztBQUFBLGNBQ2IsV0FBVztBQUFBLGNBQ1gsTUFBTSxrQkFBa0I7QUFBQSxjQUN4QixDQUFDLElBQUksT0FBTztBQUNWLHNCQUFNLE1BQU0saUJBQWlCLENBQUMsWUFBWSxRQUFRLEdBQUcsR0FBRyxPQUFPLElBQUksUUFBUSxNQUFNLENBQUMsQ0FBQyxPQUFPLE9BQU8sRUFBRTtBQUFBLGNBQ3JHO0FBQUEsWUFDRixDQUFDO0FBQUEsVUFDSDtBQUdBLGtCQUFRLE9BQU87QUFBQSxZQUNiLFdBQVc7QUFBQSxZQUNYLE1BQU0sa0JBQWtCO0FBQUEsWUFDeEIsQ0FBQyxJQUFJLE9BQU87QUFDVixvQkFBTSxNQUFNO0FBQUEsZ0JBQ1Ysa0JBQWtCO0FBQUEsZ0JBQ2xCLElBQUksV0FBVyxNQUFNLEdBQUcsV0FBVyxRQUFRLGFBQWEsQ0FBQyxDQUFDO0FBQUEsY0FDNUQ7QUFBQSxZQUNGO0FBQUEsVUFDRixDQUFDO0FBQUEsUUFDSDtBQUFBLE1BQ0Y7QUFBQSxNQUVBLGdCQUFnQixlQUF5QztBQUN2RCxZQUNFLGNBQWMsU0FBUyxTQUFTLGVBQWUsY0FDL0MsMkJBQTJCLElBQUksY0FBYyxTQUFTLElBQUksR0FDMUQ7QUFDQSxnQkFBTSxrQkFBa0IsMkJBQTJCLElBQUksY0FBYyxTQUFTLElBQUk7QUFDbEYsaUJBQU8sR0FBRyxvQkFBb0IsTUFBUztBQUN2QyxrQkFBUSxPQUFPO0FBQUEsWUFDYixXQUFXO0FBQUEsWUFDWCxNQUFNLGNBQWM7QUFBQSxZQUNwQixJQUFJLE9BQU87QUFDVCxxQkFBTyxNQUFNLFlBQVksY0FBYyxVQUFVLGVBQWU7QUFBQSxZQUNsRTtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0g7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDO0FBRUQsSUFBTyx3Q0FBUTsiLAogICJuYW1lcyI6IFtdCn0K
@@ -1,9 +1,10 @@
1
1
  // src/require-ts-extension-imports.ts
2
+ import fs from "fs";
2
3
  import { ESLintUtils } from "@typescript-eslint/utils";
4
+ import "@typescript-eslint/typescript-estree";
3
5
  import getDocumentationUrl from "./get-documentation-url.mjs";
4
6
  var ruleId = "require-ts-extension-imports";
5
7
  var REQUIRE_TS_EXTENSION_IMPORTS = "REQUIRE-TS-EXTENSION-IMPORTS";
6
- var SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services/u;
7
8
  var createRule = ESLintUtils.RuleCreator(
8
9
  (name) => getDocumentationUrl(name)
9
10
  );
@@ -29,14 +30,15 @@ var rule = createRule({
29
30
  return {
30
31
  ImportDeclaration(node) {
31
32
  const importPath = node.source.value;
32
- if (importPath.startsWith(".") && !importPath.endsWith(".ts") && !importPath.endsWith(".json")) {
33
- const isServiceTypingImport = SERVICE_TYPINGS_IMPORT_PATH_PREFIX.test(importPath);
34
- const newImportPath = isServiceTypingImport && !importPath.endsWith("/index.ts") ? `${importPath}/index.ts` : `${importPath}.ts`;
33
+ if (importPath.startsWith(".") && !importPath.endsWith(".ts") && !importPath.endsWith(".json") && fs.existsSync(importPath)) {
34
+ const stats = fs.statSync(importPath);
35
+ const isDirectory = stats.isDirectory();
36
+ const fixedPath = isDirectory ? `${importPath}/index.ts` : `${importPath}.ts`;
35
37
  context.report({
36
38
  loc: node.source.loc,
37
39
  messageId: REQUIRE_TS_EXTENSION_IMPORTS,
38
40
  *fix(fixer) {
39
- yield fixer.replaceText(node.source, `'${newImportPath}'`);
41
+ yield fixer.replaceText(node.source, `'${fixedPath}'`);
40
42
  }
41
43
  });
42
44
  }
@@ -49,4 +51,4 @@ export {
49
51
  require_ts_extension_imports_default as default,
50
52
  ruleId
51
53
  };
52
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JlcXVpcmUtdHMtZXh0ZW5zaW9uLWltcG9ydHMudHMiXSwKICAibWFwcGluZ3MiOiAiO0FBUUEsU0FBUyxtQkFBbUI7QUFDNUIsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBQ3RCLElBQU0sK0JBQStCO0FBR3JDLElBQU0scUNBQXFDO0FBRTNDLElBQU0sYUFBeUQsWUFBWTtBQUFBLEVBQVksQ0FBQyxTQUN0RixvQkFBb0IsSUFBSTtBQUMxQjtBQUVBLElBQU0sT0FBc0MsV0FBVztBQUFBLEVBQ3JELE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxJQUNULFVBQVU7QUFBQSxNQUNSLENBQUMsNEJBQTRCLEdBQUc7QUFBQSxJQUNsQztBQUFBLEVBQ0Y7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxXQUFXLFFBQVE7QUFDekIsUUFBSSxDQUFDLFNBQVMsU0FBUyxLQUFLLEdBQUc7QUFDN0IsYUFBTyxDQUFDO0FBQUEsSUFDVjtBQUNBLFdBQU87QUFBQSxNQUNMLGtCQUFrQixNQUFNO0FBQ3RCLGNBQU0sYUFBYSxLQUFLLE9BQU87QUFDL0IsWUFBSSxXQUFXLFdBQVcsR0FBRyxLQUFLLENBQUMsV0FBVyxTQUFTLEtBQUssS0FBSyxDQUFDLFdBQVcsU0FBUyxPQUFPLEdBQUc7QUFDOUYsZ0JBQU0sd0JBQXdCLG1DQUFtQyxLQUFLLFVBQVU7QUFDaEYsZ0JBQU0sZ0JBQ0oseUJBQXlCLENBQUMsV0FBVyxTQUFTLFdBQVcsSUFBSSxHQUFHLFVBQVUsY0FBYyxHQUFHLFVBQVU7QUFFdkcsa0JBQVEsT0FBTztBQUFBLFlBQ2IsS0FBSyxLQUFLLE9BQU87QUFBQSxZQUNqQixXQUFXO0FBQUEsWUFDWCxDQUFDLElBQUksT0FBTztBQUNWLG9CQUFNLE1BQU0sWUFBWSxLQUFLLFFBQVEsSUFBSSxhQUFhLEdBQUc7QUFBQSxZQUMzRDtBQUFBLFVBQ0YsQ0FBQztBQUFBLFFBQ0g7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDO0FBRUQsSUFBTyx1Q0FBUTsiLAogICJuYW1lcyI6IFtdCn0K
54
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vc3JjL3JlcXVpcmUtdHMtZXh0ZW5zaW9uLWltcG9ydHMudHMiXSwKICAibWFwcGluZ3MiOiAiO0FBUUEsT0FBTyxRQUFRO0FBQ2YsU0FBUyxtQkFBbUI7QUFDNUIsT0FBeUI7QUFDekIsT0FBTyx5QkFBeUI7QUFFekIsSUFBTSxTQUFTO0FBQ3RCLElBQU0sK0JBQStCO0FBRXJDLElBQU0sYUFBeUQsWUFBWTtBQUFBLEVBQVksQ0FBQyxTQUN0RixvQkFBb0IsSUFBSTtBQUMxQjtBQUVBLElBQU0sT0FBc0MsV0FBVztBQUFBLEVBQ3JELE1BQU07QUFBQSxFQUNOLE1BQU07QUFBQSxJQUNKLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxNQUNKLGFBQWE7QUFBQSxJQUNmO0FBQUEsSUFDQSxTQUFTO0FBQUEsSUFDVCxRQUFRLENBQUM7QUFBQSxJQUNULFVBQVU7QUFBQSxNQUNSLENBQUMsNEJBQTRCLEdBQUc7QUFBQSxJQUNsQztBQUFBLEVBQ0Y7QUFBQSxFQUNBLGdCQUFnQixDQUFDO0FBQUEsRUFDakIsT0FBTyxTQUFTO0FBQ2QsVUFBTSxXQUFXLFFBQVE7QUFDekIsUUFBSSxDQUFDLFNBQVMsU0FBUyxLQUFLLEdBQUc7QUFDN0IsYUFBTyxDQUFDO0FBQUEsSUFDVjtBQUNBLFdBQU87QUFBQSxNQUNMLGtCQUFrQixNQUFrQztBQUNsRCxjQUFNLGFBQWEsS0FBSyxPQUFPO0FBQy9CLFlBQ0UsV0FBVyxXQUFXLEdBQUcsS0FDekIsQ0FBQyxXQUFXLFNBQVMsS0FBSyxLQUMxQixDQUFDLFdBQVcsU0FBUyxPQUFPLEtBQzVCLEdBQUcsV0FBVyxVQUFVLEdBQ3hCO0FBQ0EsZ0JBQU0sUUFBUSxHQUFHLFNBQVMsVUFBVTtBQUNwQyxnQkFBTSxjQUFjLE1BQU0sWUFBWTtBQUN0QyxnQkFBTSxZQUFZLGNBQWMsR0FBRyxVQUFVLGNBQWMsR0FBRyxVQUFVO0FBQ3hFLGtCQUFRLE9BQU87QUFBQSxZQUNiLEtBQUssS0FBSyxPQUFPO0FBQUEsWUFDakIsV0FBVztBQUFBLFlBQ1gsQ0FBQyxJQUFJLE9BQU87QUFDVixvQkFBTSxNQUFNLFlBQVksS0FBSyxRQUFRLElBQUksU0FBUyxHQUFHO0FBQUEsWUFDdkQ7QUFBQSxVQUNGLENBQUM7QUFBQSxRQUNIO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxFQUNGO0FBQ0YsQ0FBQztBQUVELElBQU8sdUNBQVE7IiwKICAibmFtZXMiOiBbXQp9Cg==
@@ -1,4 +1,4 @@
1
1
  import { ESLintUtils } from '@typescript-eslint/utils';
2
2
  export declare const ruleId = "require-fixed-services-import";
3
- declare const rule: ESLintUtils.RuleModule<'updateServicesImportFrom'>;
3
+ declare const rule: ESLintUtils.RuleModule<'updateServicesImportSpecifier' | 'updateServicesImportSource' | 'renameServiceTypeReference'>;
4
4
  export default rule;
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@checkdigit/eslint-plugin","version":"7.7.0-PR.107-46e4","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-mjs","!src/**/test/**","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/test/**","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-mjs/**/test/**","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 .","lint:fix":"eslint --max-warnings 0 --fix .","prepare":"","prepublishOnly":"npm run build:dist-types && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"^8.18.0","@typescript-eslint/utils":"^8.18.0","ts-api-utils":"^2.0.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^6.0.0","@checkdigit/typescript-config":"^9.0.0","@eslint/js":"^9.16.0","@types/eslint":"^9.6.1","@types/eslint-config-prettier":"^6.11.3","@typescript-eslint/parser":"^8.18.0","@typescript-eslint/rule-tester":"^8.18.0","eslint":"^9.16.0","eslint-config-prettier":"^9.1.0","eslint-import-resolver-typescript":"^3.7.0","eslint-plugin-eslint-plugin":"^6.3.2","eslint-plugin-import":"^2.31.0","eslint-plugin-no-only-tests":"^3.3.0","eslint-plugin-no-secrets":"^2.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"1.0.4","http-status-codes":"^2.3.0","rimraf":"^6.0.1","typescript-eslint":"^8.18.0"},"peerDependencies":{"eslint":">=9 <10"},"engines":{"node":">=20.17"}}
1
+ {"name":"@checkdigit/eslint-plugin","version":"7.7.0-PR.107-e11e","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-mjs","!src/**/test/**","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/test/**","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-mjs/**/test/**","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 .","lint:fix":"eslint --max-warnings 0 --fix .","prepare":"","prepublishOnly":"npm run build:dist-types && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@typescript-eslint/type-utils":"^8.18.0","@typescript-eslint/utils":"^8.18.0","ts-api-utils":"^2.0.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^6.0.0","@checkdigit/typescript-config":"^9.0.0","@eslint/js":"^9.16.0","@types/eslint":"^9.6.1","@types/eslint-config-prettier":"^6.11.3","@typescript-eslint/parser":"^8.18.0","@typescript-eslint/rule-tester":"^8.18.0","eslint":"^9.16.0","eslint-config-prettier":"^9.1.0","eslint-import-resolver-typescript":"^3.7.0","eslint-plugin-eslint-plugin":"^6.3.2","eslint-plugin-import":"^2.31.0","eslint-plugin-no-only-tests":"^3.3.0","eslint-plugin-no-secrets":"^2.1.1","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"1.0.4","http-status-codes":"^2.3.0","rimraf":"^6.0.1","typescript-eslint":"^8.18.0"},"peerDependencies":{"eslint":">=9 <10"},"engines":{"node":">=20.17"}}
@@ -6,16 +6,22 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
- import { ESLintUtils } from '@typescript-eslint/utils';
9
+ import { strict as assert } from 'node:assert';
10
+
11
+ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
12
 
11
13
  import getDocumentationUrl from './get-documentation-url.ts';
12
14
 
13
15
  export const ruleId = 'require-fixed-services-import';
14
16
 
15
17
  const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
16
- const SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
18
+ const SERVICE_TYPINGS_IMPORT_PATH = /(?<path>\.\.\/)+services(?!\/index(?:\.ts)?)\/.*/u;
19
+ const SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION =
20
+ /(?<path>\.\.\/)+services\/(?<service>\w+)\/(?<version>v\d+)(?<index>\/index(?:\.ts)?)?/u;
17
21
 
18
- const rule: ESLintUtils.RuleModule<'updateServicesImportFrom'> = createRule({
22
+ const rule: ESLintUtils.RuleModule<
23
+ 'updateServicesImportSpecifier' | 'updateServicesImportSource' | 'renameServiceTypeReference'
24
+ > = createRule({
19
25
  name: ruleId,
20
26
  meta: {
21
27
  type: 'suggestion',
@@ -23,29 +29,82 @@ const rule: ESLintUtils.RuleModule<'updateServicesImportFrom'> = createRule({
23
29
  description: 'Require fixed "from" with service typing imports from "src/services".',
24
30
  },
25
31
  messages: {
26
- updateServicesImportFrom: 'Update service typing imports to be from the fixed "src/services" path.',
32
+ updateServicesImportSpecifier:
33
+ 'Update service typing import specifiers to be from the corresponding service version namespace.',
34
+ updateServicesImportSource: 'Update service typing imports to be from the fixed "src/services" path.',
35
+ renameServiceTypeReference: 'Rename service type reference using the corresponding service version namespace.',
27
36
  },
28
37
  fixable: 'code',
29
38
  schema: [],
30
39
  },
31
40
  defaultOptions: [],
32
41
  create(context) {
42
+ const importedServiceTypeMapping = new Map<string, string>();
43
+
33
44
  return {
34
- ImportDeclaration(node) {
35
- const moduleName = node.source.value;
36
- if (SERVICE_TYPINGS_IMPORT_PATH_PREFIX.test(moduleName)) {
45
+ ImportDeclaration(importDeclaration) {
46
+ const moduleName = importDeclaration.source.value;
47
+ if (SERVICE_TYPINGS_IMPORT_PATH.test(moduleName)) {
48
+ const match = SERVICE_TYPINGS_IMPORT_PATH_WITH_VERSION.exec(moduleName);
49
+ if (match?.groups) {
50
+ // need to import the service typings from the fixed path, and also apply the namespace to the referenced types
51
+ const { service, version } = match.groups;
52
+ assert.ok(service !== undefined && version !== undefined);
53
+
54
+ // remember the type name and the corresponding service, which will be used to rename the references
55
+ for (const specifier of importDeclaration.specifiers) {
56
+ if (
57
+ specifier.type === AST_NODE_TYPES.ImportSpecifier &&
58
+ specifier.imported.type === AST_NODE_TYPES.Identifier
59
+ ) {
60
+ importedServiceTypeMapping.set(specifier.local.name, `${service}.${specifier.imported.name}`);
61
+ }
62
+ }
63
+
64
+ const rangeStart = importDeclaration.specifiers[0]?.range[0];
65
+ assert.ok(rangeStart !== undefined);
66
+ const rangeEnd = importDeclaration.specifiers.at(-1)?.range[1];
67
+ assert.ok(rangeEnd !== undefined);
68
+ // import the service typings using our naming convension
69
+ context.report({
70
+ messageId: 'updateServicesImportSpecifier',
71
+ node: importDeclaration.source,
72
+ *fix(fixer) {
73
+ yield fixer.replaceTextRange([rangeStart, rangeEnd], `${service}V${version.slice(1)} as ${service}`);
74
+ },
75
+ });
76
+ }
77
+
78
+ // update the imported source to be the fixed path
37
79
  context.report({
38
- messageId: 'updateServicesImportFrom',
39
- node: node.source,
80
+ messageId: 'updateServicesImportSource',
81
+ node: importDeclaration.source,
40
82
  *fix(fixer) {
41
83
  yield fixer.replaceText(
42
- node.source,
43
- `'${moduleName.slice(0, moduleName.indexOf('../services') + '../services'.length)}'`,
84
+ importDeclaration.source,
85
+ `'${moduleName.slice(0, moduleName.indexOf('../services'))}../services/index.ts'`,
44
86
  );
45
87
  },
46
88
  });
47
89
  }
48
90
  },
91
+
92
+ TSTypeReference(typeReference: TSESTree.TSTypeReference) {
93
+ if (
94
+ typeReference.typeName.type === AST_NODE_TYPES.Identifier &&
95
+ importedServiceTypeMapping.has(typeReference.typeName.name)
96
+ ) {
97
+ const renamedTypeName = importedServiceTypeMapping.get(typeReference.typeName.name);
98
+ assert.ok(renamedTypeName !== undefined);
99
+ context.report({
100
+ messageId: 'renameServiceTypeReference',
101
+ node: typeReference.typeName,
102
+ fix(fixer) {
103
+ return fixer.replaceText(typeReference.typeName, renamedTypeName);
104
+ },
105
+ });
106
+ }
107
+ },
49
108
  };
50
109
  },
51
110
  });
@@ -6,15 +6,14 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
+ import fs from 'fs';
9
10
  import { ESLintUtils } from '@typescript-eslint/utils';
11
+ import { TSESTree } from '@typescript-eslint/typescript-estree';
10
12
  import getDocumentationUrl from './get-documentation-url.ts';
11
13
 
12
14
  export const ruleId = 'require-ts-extension-imports';
13
15
  const REQUIRE_TS_EXTENSION_IMPORTS = 'REQUIRE-TS-EXTENSION-IMPORTS';
14
16
 
15
- // Matches paths that start with one or more ../, followed by services
16
- const SERVICE_TYPINGS_IMPORT_PATH_PREFIX = /(?<path>\.\.\/)+services/u;
17
-
18
17
  const createRule: ReturnType<typeof ESLintUtils.RuleCreator> = ESLintUtils.RuleCreator((name) =>
19
18
  getDocumentationUrl(name),
20
19
  );
@@ -39,18 +38,22 @@ const rule: ReturnType<typeof createRule> = createRule({
39
38
  return {};
40
39
  }
41
40
  return {
42
- ImportDeclaration(node) {
41
+ ImportDeclaration(node: TSESTree.ImportDeclaration) {
43
42
  const importPath = node.source.value;
44
- if (importPath.startsWith('.') && !importPath.endsWith('.ts') && !importPath.endsWith('.json')) {
45
- const isServiceTypingImport = SERVICE_TYPINGS_IMPORT_PATH_PREFIX.test(importPath);
46
- const newImportPath =
47
- isServiceTypingImport && !importPath.endsWith('/index.ts') ? `${importPath}/index.ts` : `${importPath}.ts`;
48
-
43
+ if (
44
+ importPath.startsWith('.') &&
45
+ !importPath.endsWith('.ts') &&
46
+ !importPath.endsWith('.json') &&
47
+ fs.existsSync(importPath)
48
+ ) {
49
+ const stats = fs.statSync(importPath);
50
+ const isDirectory = stats.isDirectory();
51
+ const fixedPath = isDirectory ? `${importPath}/index.ts` : `${importPath}.ts`;
49
52
  context.report({
50
53
  loc: node.source.loc,
51
54
  messageId: REQUIRE_TS_EXTENSION_IMPORTS,
52
55
  *fix(fixer) {
53
- yield fixer.replaceText(node.source, `'${newImportPath}'`);
56
+ yield fixer.replaceText(node.source, `'${fixedPath}'`);
54
57
  },
55
58
  });
56
59
  }