@trackunit/eslint-plugin-trackunit 0.0.2
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/CHANGELOG.md +9 -0
- package/README.md +117 -0
- package/package.json +31 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/config/fragments/ignores.d.ts +2 -0
- package/src/lib/config/fragments/ignores.js +18 -0
- package/src/lib/config/fragments/ignores.js.map +1 -0
- package/src/lib/config/fragments/import-rules.d.ts +3 -0
- package/src/lib/config/fragments/import-rules.js +58 -0
- package/src/lib/config/fragments/import-rules.js.map +1 -0
- package/src/lib/config/fragments/jest-overrides.d.ts +2 -0
- package/src/lib/config/fragments/jest-overrides.js +30 -0
- package/src/lib/config/fragments/jest-overrides.js.map +1 -0
- package/src/lib/config/fragments/jsdoc-rules.d.ts +3 -0
- package/src/lib/config/fragments/jsdoc-rules.js +71 -0
- package/src/lib/config/fragments/jsdoc-rules.js.map +1 -0
- package/src/lib/config/fragments/module-boundaries.d.ts +2 -0
- package/src/lib/config/fragments/module-boundaries.js +92 -0
- package/src/lib/config/fragments/module-boundaries.js.map +1 -0
- package/src/lib/config/fragments/react-rules.d.ts +5 -0
- package/src/lib/config/fragments/react-rules.js +137 -0
- package/src/lib/config/fragments/react-rules.js.map +1 -0
- package/src/lib/config/fragments/restricted-imports.d.ts +2 -0
- package/src/lib/config/fragments/restricted-imports.js +58 -0
- package/src/lib/config/fragments/restricted-imports.js.map +1 -0
- package/src/lib/config/fragments/testing-library.d.ts +2 -0
- package/src/lib/config/fragments/testing-library.js +7 -0
- package/src/lib/config/fragments/testing-library.js.map +1 -0
- package/src/lib/config/fragments/typescript-rules.d.ts +2 -0
- package/src/lib/config/fragments/typescript-rules.js +97 -0
- package/src/lib/config/fragments/typescript-rules.js.map +1 -0
- package/src/lib/config/index.d.ts +863 -0
- package/src/lib/config/index.js +10 -0
- package/src/lib/config/index.js.map +1 -0
- package/src/lib/config/plugins.d.ts +90 -0
- package/src/lib/config/plugins.js +44 -0
- package/src/lib/config/plugins.js.map +1 -0
- package/src/lib/config/presets/base.d.ts +265 -0
- package/src/lib/config/presets/base.js +145 -0
- package/src/lib/config/presets/base.js.map +1 -0
- package/src/lib/config/presets/e2e.d.ts +10 -0
- package/src/lib/config/presets/e2e.js +19 -0
- package/src/lib/config/presets/e2e.js.map +1 -0
- package/src/lib/config/presets/public-api.d.ts +147 -0
- package/src/lib/config/presets/public-api.js +62 -0
- package/src/lib/config/presets/public-api.js.map +1 -0
- package/src/lib/config/presets/react.d.ts +598 -0
- package/src/lib/config/presets/react.js +97 -0
- package/src/lib/config/presets/react.js.map +1 -0
- package/src/lib/config/presets/server.d.ts +36 -0
- package/src/lib/config/presets/server.js +37 -0
- package/src/lib/config/presets/server.js.map +1 -0
- package/src/lib/config/utils.d.ts +6 -0
- package/src/lib/config/utils.js +28 -0
- package/src/lib/config/utils.js.map +1 -0
- package/src/lib/config-helpers/create-skip-when.d.ts +35 -0
- package/src/lib/config-helpers/create-skip-when.js +54 -0
- package/src/lib/config-helpers/create-skip-when.js.map +1 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.d.ts +16 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js +83 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +1 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.d.ts +4 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js +297 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js.map +1 -0
- package/src/lib/rules/no-internal-barrel-files/examples.d.ts +80 -0
- package/src/lib/rules/no-internal-barrel-files/examples.js +84 -0
- package/src/lib/rules/no-internal-barrel-files/examples.js.map +1 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.d.ts +29 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js +178 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +1 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.d.ts +5 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js +67 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +1 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.d.ts +2 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js +34 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +1 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.d.ts +16 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js +55 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +1 -0
- package/src/lib/rules/no-typescript-assertion/examples.d.ts +1 -0
- package/src/lib/rules/no-typescript-assertion/examples.js +45 -0
- package/src/lib/rules/no-typescript-assertion/examples.js.map +1 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.d.ts +20 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js +83 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +1 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.d.ts +73 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js +333 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.d.ts +56 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js +225 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.d.ts +49 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js +75 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.d.ts +32 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js +143 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.d.ts +27 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js +196 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.d.ts +76 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js +245 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +1 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.d.ts +4 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.js +289 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +1 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.d.ts +26 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js +402 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +1 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.d.ts +13 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js +271 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +1 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.d.ts +15 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js +245 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +1 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.d.ts +17 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js +133 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +1 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.d.ts +12 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js +128 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +1 -0
- package/src/lib/rules-map.d.ts +66 -0
- package/src/lib/rules-map.js +34 -0
- package/src/lib/rules-map.js.map +1 -0
- package/src/lib/utils/ast-utils.d.ts +85 -0
- package/src/lib/utils/ast-utils.js +530 -0
- package/src/lib/utils/ast-utils.js.map +1 -0
- package/src/lib/utils/classname-utils.d.ts +150 -0
- package/src/lib/utils/classname-utils.js +492 -0
- package/src/lib/utils/classname-utils.js.map +1 -0
- package/src/lib/utils/file-utils.d.ts +14 -0
- package/src/lib/utils/file-utils.js +106 -0
- package/src/lib/utils/file-utils.js.map +1 -0
- package/src/lib/utils/import-utils.d.ts +85 -0
- package/src/lib/utils/import-utils.js +193 -0
- package/src/lib/utils/import-utils.js.map +1 -0
- package/src/lib/utils/nx-utils.d.ts +59 -0
- package/src/lib/utils/nx-utils.js +103 -0
- package/src/lib/utils/nx-utils.js.map +1 -0
- package/src/lib/utils/package-utils.d.ts +38 -0
- package/src/lib/utils/package-utils.js +74 -0
- package/src/lib/utils/package-utils.js.map +1 -0
- package/src/lib/utils/typescript-utils.d.ts +29 -0
- package/src/lib/utils/typescript-utils.js +213 -0
- package/src/lib/utils/typescript-utils.js.map +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearMetadataCache = exports.projectMatchesCriteria = exports.getProjectMetadata = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const file_utils_1 = require("./file-utils");
|
|
7
|
+
/**
|
|
8
|
+
* Cache for project metadata to avoid reading files multiple times.
|
|
9
|
+
* Key is the absolute path to project.json, value is the parsed metadata.
|
|
10
|
+
*/
|
|
11
|
+
const metadataCache = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Get NX project metadata from the nearest project.json file.
|
|
14
|
+
*
|
|
15
|
+
* @param filePath - Absolute path to a file in the project
|
|
16
|
+
* @returns Project metadata or null if no project.json found
|
|
17
|
+
* @example
|
|
18
|
+
* const metadata = getProjectMetadata("/workspace/libs/my-lib/src/index.ts");
|
|
19
|
+
* // Returns: { name: "my-lib", tags: ["type:react"], targets: ["build", "test"], ... }
|
|
20
|
+
*/
|
|
21
|
+
const getProjectMetadata = (filePath) => {
|
|
22
|
+
const projectJsonPath = (0, file_utils_1.findNearestFile)(filePath, "project.json");
|
|
23
|
+
if (!projectJsonPath) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
// Check cache first
|
|
27
|
+
if (metadataCache.has(projectJsonPath)) {
|
|
28
|
+
return metadataCache.get(projectJsonPath) ?? null;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(projectJsonPath, "utf8");
|
|
32
|
+
const projectJson = JSON.parse(content);
|
|
33
|
+
const metadata = {
|
|
34
|
+
name: projectJson.name ?? "",
|
|
35
|
+
tags: projectJson.tags ?? [],
|
|
36
|
+
targets: typeof projectJson.targets === "object" && projectJson.targets !== null ? Object.keys(projectJson.targets) : [],
|
|
37
|
+
projectJsonPath,
|
|
38
|
+
};
|
|
39
|
+
// Cache the result
|
|
40
|
+
metadataCache.set(projectJsonPath, metadata);
|
|
41
|
+
return metadata;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
exports.getProjectMetadata = getProjectMetadata;
|
|
48
|
+
/**
|
|
49
|
+
* Check if project metadata matches the given criteria.
|
|
50
|
+
*
|
|
51
|
+
* @param metadata - Project metadata to check
|
|
52
|
+
* @param criteria - Criteria to match against
|
|
53
|
+
* @returns True if metadata matches all criteria
|
|
54
|
+
* @example
|
|
55
|
+
* // Check if project has the "scope:tool" tag
|
|
56
|
+
* projectMatchesCriteria(metadata, { tags: ["scope:tool"] })
|
|
57
|
+
* @example
|
|
58
|
+
* // Check if project has ALL specified tags
|
|
59
|
+
* projectMatchesCriteria(metadata, {
|
|
60
|
+
* tags: ["scope:tool", "type:vanilla"],
|
|
61
|
+
* tagsMatchMode: "every"
|
|
62
|
+
* })
|
|
63
|
+
* @example
|
|
64
|
+
* // Check if project has ANY of the specified tags
|
|
65
|
+
* projectMatchesCriteria(metadata, {
|
|
66
|
+
* tags: ["scope:tool", "scope:client"],
|
|
67
|
+
* tagsMatchMode: "some"
|
|
68
|
+
* })
|
|
69
|
+
* @example
|
|
70
|
+
* // Check if project has specific targets
|
|
71
|
+
* projectMatchesCriteria(metadata, { targets: ["build", "test"] })
|
|
72
|
+
*/
|
|
73
|
+
const projectMatchesCriteria = (metadata, criteria) => {
|
|
74
|
+
const { tags, targets, tagsMatchMode = "some", targetsMatchMode = "some" } = criteria;
|
|
75
|
+
// Check tags if specified
|
|
76
|
+
if (tags && tags.length > 0) {
|
|
77
|
+
const hasMatchingTags = tagsMatchMode === "every"
|
|
78
|
+
? tags.every(tag => metadata.tags.includes(tag))
|
|
79
|
+
: tags.some(tag => metadata.tags.includes(tag));
|
|
80
|
+
if (!hasMatchingTags) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Check targets if specified
|
|
85
|
+
if (targets && targets.length > 0) {
|
|
86
|
+
const hasMatchingTargets = targetsMatchMode === "every"
|
|
87
|
+
? targets.every(target => metadata.targets.includes(target))
|
|
88
|
+
: targets.some(target => metadata.targets.includes(target));
|
|
89
|
+
if (!hasMatchingTargets) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
};
|
|
95
|
+
exports.projectMatchesCriteria = projectMatchesCriteria;
|
|
96
|
+
/**
|
|
97
|
+
* Clear the metadata cache. Useful for testing.
|
|
98
|
+
*/
|
|
99
|
+
const clearMetadataCache = () => {
|
|
100
|
+
metadataCache.clear();
|
|
101
|
+
};
|
|
102
|
+
exports.clearMetadataCache = clearMetadataCache;
|
|
103
|
+
//# sourceMappingURL=nx-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nx-utils.js","sourceRoot":"","sources":["../../../../../../../libs/eslint/plugin-trackunit/src/lib/utils/nx-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"fs\";\nimport { findNearestFile } from \"./file-utils\";\n\n/**\n * Utility functions for working with NX project metadata in ESLint rules.\n *\n * ## Main Functions\n * - `getProjectMetadata()` - Get NX project metadata from project.json\n * - `projectMatchesCriteria()` - Check if project matches given criteria\n */\n\nexport type ProjectMetadata = {\n name: string;\n tags: Array<string>;\n targets: Array<string>;\n projectJsonPath: string;\n};\n\nexport type MatchCriteria = {\n tags?: Array<string>;\n targets?: Array<string>;\n tagsMatchMode?: \"some\" | \"every\";\n targetsMatchMode?: \"some\" | \"every\";\n};\n\n/**\n * Cache for project metadata to avoid reading files multiple times.\n * Key is the absolute path to project.json, value is the parsed metadata.\n */\nconst metadataCache = new Map<string, ProjectMetadata>();\n\n/**\n * Get NX project metadata from the nearest project.json file.\n *\n * @param filePath - Absolute path to a file in the project\n * @returns Project metadata or null if no project.json found\n * @example\n * const metadata = getProjectMetadata(\"/workspace/libs/my-lib/src/index.ts\");\n * // Returns: { name: \"my-lib\", tags: [\"type:react\"], targets: [\"build\", \"test\"], ... }\n */\nexport const getProjectMetadata = (filePath: string): ProjectMetadata | null => {\n const projectJsonPath = findNearestFile(filePath, \"project.json\");\n\n if (!projectJsonPath) {\n return null;\n }\n\n // Check cache first\n if (metadataCache.has(projectJsonPath)) {\n return metadataCache.get(projectJsonPath) ?? null;\n }\n\n try {\n const content = fs.readFileSync(projectJsonPath, \"utf8\");\n const projectJson = JSON.parse(content);\n\n const metadata: ProjectMetadata = {\n name: projectJson.name ?? \"\",\n tags: projectJson.tags ?? [],\n targets:\n typeof projectJson.targets === \"object\" && projectJson.targets !== null ? Object.keys(projectJson.targets) : [],\n projectJsonPath,\n };\n\n // Cache the result\n metadataCache.set(projectJsonPath, metadata);\n\n return metadata;\n } catch {\n return null;\n }\n};\n\n/**\n * Check if project metadata matches the given criteria.\n *\n * @param metadata - Project metadata to check\n * @param criteria - Criteria to match against\n * @returns True if metadata matches all criteria\n * @example\n * // Check if project has the \"scope:tool\" tag\n * projectMatchesCriteria(metadata, { tags: [\"scope:tool\"] })\n * @example\n * // Check if project has ALL specified tags\n * projectMatchesCriteria(metadata, {\n * tags: [\"scope:tool\", \"type:vanilla\"],\n * tagsMatchMode: \"every\"\n * })\n * @example\n * // Check if project has ANY of the specified tags\n * projectMatchesCriteria(metadata, {\n * tags: [\"scope:tool\", \"scope:client\"],\n * tagsMatchMode: \"some\"\n * })\n * @example\n * // Check if project has specific targets\n * projectMatchesCriteria(metadata, { targets: [\"build\", \"test\"] })\n */\nexport const projectMatchesCriteria = (metadata: ProjectMetadata, criteria: MatchCriteria): boolean => {\n const { tags, targets, tagsMatchMode = \"some\", targetsMatchMode = \"some\" } = criteria;\n\n // Check tags if specified\n if (tags && tags.length > 0) {\n const hasMatchingTags =\n tagsMatchMode === \"every\"\n ? tags.every(tag => metadata.tags.includes(tag))\n : tags.some(tag => metadata.tags.includes(tag));\n\n if (!hasMatchingTags) {\n return false;\n }\n }\n\n // Check targets if specified\n if (targets && targets.length > 0) {\n const hasMatchingTargets =\n targetsMatchMode === \"every\"\n ? targets.every(target => metadata.targets.includes(target))\n : targets.some(target => metadata.targets.includes(target));\n\n if (!hasMatchingTargets) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Clear the metadata cache. Useful for testing.\n */\nexport const clearMetadataCache = (): void => {\n metadataCache.clear();\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for working with package.json and npm packages in ESLint rules.
|
|
3
|
+
*
|
|
4
|
+
* ## Main Functions
|
|
5
|
+
* - `getDependencies()` - Get dependencies of a specific type from package.json
|
|
6
|
+
* - `getPackageNameFromImport()` - Extract base package name from import source
|
|
7
|
+
*/
|
|
8
|
+
export type DependencyType = "dependencies" | "devDependencies" | "all";
|
|
9
|
+
/**
|
|
10
|
+
* Get dependencies from package.json based on the specified type.
|
|
11
|
+
*
|
|
12
|
+
* @param packageJsonPath - Absolute path to package.json
|
|
13
|
+
* @param type - Type of dependencies to retrieve
|
|
14
|
+
* @returns Set of package names
|
|
15
|
+
* @example
|
|
16
|
+
* // Get only production dependencies
|
|
17
|
+
* const deps = getDependencies("/path/to/package.json", "dependencies");
|
|
18
|
+
* @example
|
|
19
|
+
* // Get only dev dependencies
|
|
20
|
+
* const devDeps = getDependencies("/path/to/package.json", "devDependencies");
|
|
21
|
+
* @example
|
|
22
|
+
* // Get all dependencies (union of both)
|
|
23
|
+
* const allDeps = getDependencies("/path/to/package.json", "all");
|
|
24
|
+
*/
|
|
25
|
+
export declare const getDependencies: (packageJsonPath: string, type: DependencyType) => Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the base package name from an import source.
|
|
28
|
+
* Handles scoped packages and sub-path imports.
|
|
29
|
+
*
|
|
30
|
+
* @param importSource - The import source from an ImportDeclaration node
|
|
31
|
+
* @returns The base package name
|
|
32
|
+
* @example
|
|
33
|
+
* getPackageNameFromImport("lodash/fp") // → "lodash"
|
|
34
|
+
* getPackageNameFromImport("@scope/package/sub") // → "@scope/package"
|
|
35
|
+
* getPackageNameFromImport("react") // → "react"
|
|
36
|
+
* getPackageNameFromImport("./relative") // → "./relative" (caller should filter)
|
|
37
|
+
*/
|
|
38
|
+
export declare const getPackageNameFromImport: (importSource: string) => string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPackageNameFromImport = exports.getDependencies = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
/**
|
|
7
|
+
* Get dependencies from package.json based on the specified type.
|
|
8
|
+
*
|
|
9
|
+
* @param packageJsonPath - Absolute path to package.json
|
|
10
|
+
* @param type - Type of dependencies to retrieve
|
|
11
|
+
* @returns Set of package names
|
|
12
|
+
* @example
|
|
13
|
+
* // Get only production dependencies
|
|
14
|
+
* const deps = getDependencies("/path/to/package.json", "dependencies");
|
|
15
|
+
* @example
|
|
16
|
+
* // Get only dev dependencies
|
|
17
|
+
* const devDeps = getDependencies("/path/to/package.json", "devDependencies");
|
|
18
|
+
* @example
|
|
19
|
+
* // Get all dependencies (union of both)
|
|
20
|
+
* const allDeps = getDependencies("/path/to/package.json", "all");
|
|
21
|
+
*/
|
|
22
|
+
const getDependencies = (packageJsonPath, type) => {
|
|
23
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
|
|
24
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
25
|
+
const dependencies = packageJson.dependencies ?? {};
|
|
26
|
+
const devDependencies = packageJson.devDependencies ?? {};
|
|
27
|
+
switch (type) {
|
|
28
|
+
case "dependencies":
|
|
29
|
+
return new Set(Object.keys(dependencies));
|
|
30
|
+
case "devDependencies":
|
|
31
|
+
return new Set(Object.keys(devDependencies));
|
|
32
|
+
case "all":
|
|
33
|
+
return new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)]);
|
|
34
|
+
default: {
|
|
35
|
+
const exhaustiveCheck = type;
|
|
36
|
+
throw new Error(`Unknown dependency type: ${exhaustiveCheck}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.getDependencies = getDependencies;
|
|
41
|
+
/**
|
|
42
|
+
* Extract the base package name from an import source.
|
|
43
|
+
* Handles scoped packages and sub-path imports.
|
|
44
|
+
*
|
|
45
|
+
* @param importSource - The import source from an ImportDeclaration node
|
|
46
|
+
* @returns The base package name
|
|
47
|
+
* @example
|
|
48
|
+
* getPackageNameFromImport("lodash/fp") // → "lodash"
|
|
49
|
+
* getPackageNameFromImport("@scope/package/sub") // → "@scope/package"
|
|
50
|
+
* getPackageNameFromImport("react") // → "react"
|
|
51
|
+
* getPackageNameFromImport("./relative") // → "./relative" (caller should filter)
|
|
52
|
+
*/
|
|
53
|
+
const getPackageNameFromImport = (importSource) => {
|
|
54
|
+
// Handle relative imports - return as-is, caller should filter
|
|
55
|
+
if (importSource.startsWith(".") || importSource.startsWith("/")) {
|
|
56
|
+
return importSource;
|
|
57
|
+
}
|
|
58
|
+
// Handle scoped packages: @scope/package/sub-path → @scope/package
|
|
59
|
+
if (importSource.startsWith("@")) {
|
|
60
|
+
const parts = importSource.split("/");
|
|
61
|
+
if (parts.length >= 2) {
|
|
62
|
+
return `${parts[0]}/${parts[1]}`;
|
|
63
|
+
}
|
|
64
|
+
return importSource;
|
|
65
|
+
}
|
|
66
|
+
// Handle regular packages: package/sub-path → package
|
|
67
|
+
const firstSlash = importSource.indexOf("/");
|
|
68
|
+
if (firstSlash !== -1) {
|
|
69
|
+
return importSource.substring(0, firstSlash);
|
|
70
|
+
}
|
|
71
|
+
return importSource;
|
|
72
|
+
};
|
|
73
|
+
exports.getPackageNameFromImport = getPackageNameFromImport;
|
|
74
|
+
//# sourceMappingURL=package-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-utils.js","sourceRoot":"","sources":["../../../../../../../libs/eslint/plugin-trackunit/src/lib/utils/package-utils.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AAiBzB;;;;;;;;;;;;;;;GAeG;AACI,MAAM,eAAe,GAAG,CAAC,eAAuB,EAAE,IAAoB,EAAe,EAAE;IAC5F,MAAM,kBAAkB,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IACpE,MAAM,WAAW,GAAgB,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAEhE,MAAM,YAAY,GAAG,WAAW,CAAC,YAAY,IAAI,EAAE,CAAC;IACpD,MAAM,eAAe,GAAG,WAAW,CAAC,eAAe,IAAI,EAAE,CAAC;IAE1D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,cAAc;YACjB,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QAC5C,KAAK,iBAAiB;YACpB,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;QAC/C,KAAK,KAAK;YACR,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAClF,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,eAAe,GAAU,IAAI,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,4BAA4B,eAAe,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAnBW,QAAA,eAAe,mBAmB1B;AAEF;;;;;;;;;;;GAWG;AACI,MAAM,wBAAwB,GAAG,CAAC,YAAoB,EAAU,EAAE;IACvE,+DAA+D;IAC/D,IAAI,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,mEAAmE;IACnE,IAAI,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,sDAAsD;IACtD,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAtBW,QAAA,wBAAwB,4BAsBnC","sourcesContent":["import * as fs from \"fs\";\n\n/**\n * Utility functions for working with package.json and npm packages in ESLint rules.\n *\n * ## Main Functions\n * - `getDependencies()` - Get dependencies of a specific type from package.json\n * - `getPackageNameFromImport()` - Extract base package name from import source\n */\n\nexport type DependencyType = \"dependencies\" | \"devDependencies\" | \"all\";\n\ntype PackageJson = {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n};\n\n/**\n * Get dependencies from package.json based on the specified type.\n *\n * @param packageJsonPath - Absolute path to package.json\n * @param type - Type of dependencies to retrieve\n * @returns Set of package names\n * @example\n * // Get only production dependencies\n * const deps = getDependencies(\"/path/to/package.json\", \"dependencies\");\n * @example\n * // Get only dev dependencies\n * const devDeps = getDependencies(\"/path/to/package.json\", \"devDependencies\");\n * @example\n * // Get all dependencies (union of both)\n * const allDeps = getDependencies(\"/path/to/package.json\", \"all\");\n */\nexport const getDependencies = (packageJsonPath: string, type: DependencyType): Set<string> => {\n const packageJsonContent = fs.readFileSync(packageJsonPath, \"utf8\");\n const packageJson: PackageJson = JSON.parse(packageJsonContent);\n\n const dependencies = packageJson.dependencies ?? {};\n const devDependencies = packageJson.devDependencies ?? {};\n\n switch (type) {\n case \"dependencies\":\n return new Set(Object.keys(dependencies));\n case \"devDependencies\":\n return new Set(Object.keys(devDependencies));\n case \"all\":\n return new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)]);\n default: {\n const exhaustiveCheck: never = type;\n throw new Error(`Unknown dependency type: ${exhaustiveCheck}`);\n }\n }\n};\n\n/**\n * Extract the base package name from an import source.\n * Handles scoped packages and sub-path imports.\n *\n * @param importSource - The import source from an ImportDeclaration node\n * @returns The base package name\n * @example\n * getPackageNameFromImport(\"lodash/fp\") // → \"lodash\"\n * getPackageNameFromImport(\"@scope/package/sub\") // → \"@scope/package\"\n * getPackageNameFromImport(\"react\") // → \"react\"\n * getPackageNameFromImport(\"./relative\") // → \"./relative\" (caller should filter)\n */\nexport const getPackageNameFromImport = (importSource: string): string => {\n // Handle relative imports - return as-is, caller should filter\n if (importSource.startsWith(\".\") || importSource.startsWith(\"/\")) {\n return importSource;\n }\n\n // Handle scoped packages: @scope/package/sub-path → @scope/package\n if (importSource.startsWith(\"@\")) {\n const parts = importSource.split(\"/\");\n if (parts.length >= 2) {\n return `${parts[0]}/${parts[1]}`;\n }\n return importSource;\n }\n\n // Handle regular packages: package/sub-path → package\n const firstSlash = importSource.indexOf(\"/\");\n if (firstSlash !== -1) {\n return importSource.substring(0, firstSlash);\n }\n\n return importSource;\n};\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
export type OptionalProp = {
|
|
3
|
+
name: string;
|
|
4
|
+
type: "boolean";
|
|
5
|
+
} | {
|
|
6
|
+
name: string;
|
|
7
|
+
type: "string";
|
|
8
|
+
} | {
|
|
9
|
+
name: string;
|
|
10
|
+
type: "number";
|
|
11
|
+
} | {
|
|
12
|
+
name: string;
|
|
13
|
+
type: "object";
|
|
14
|
+
} | {
|
|
15
|
+
name: string;
|
|
16
|
+
type: "stringLiteral";
|
|
17
|
+
stringLiterals: Array<string>;
|
|
18
|
+
} | {
|
|
19
|
+
name: string;
|
|
20
|
+
type: "enum";
|
|
21
|
+
enumValues: Array<string | number>;
|
|
22
|
+
} | {
|
|
23
|
+
name: string;
|
|
24
|
+
type: "unknown";
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Helper function to extract optional properties from a TypeScript type annotation
|
|
28
|
+
*/
|
|
29
|
+
export declare const getOptionalPropsFromType: (context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>, typeNode: TSESTree.TypeNode | undefined) => Array<OptionalProp>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOptionalPropsFromType = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
const ts = tslib_1.__importStar(require("typescript"));
|
|
7
|
+
function isStringLiteralType(type) {
|
|
8
|
+
return (type.flags & ts.TypeFlags.StringLiteral) !== 0 && "value" in type;
|
|
9
|
+
}
|
|
10
|
+
function isEnumLiteralType(type) {
|
|
11
|
+
return (type.flags & ts.TypeFlags.EnumLiteral) !== 0 && "value" in type;
|
|
12
|
+
}
|
|
13
|
+
// Type guard for TSESTree literal nodes
|
|
14
|
+
function isTSLiteralTypeWithStringLiteral(node) {
|
|
15
|
+
return (node.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
16
|
+
node.literal.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
17
|
+
typeof node.literal.value === "string");
|
|
18
|
+
}
|
|
19
|
+
function isTSLiteralTypeWithLiteral(node) {
|
|
20
|
+
return node.type === utils_1.AST_NODE_TYPES.TSLiteralType && node.literal.type === utils_1.AST_NODE_TYPES.Literal;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Unified helper function to analyze prop types
|
|
24
|
+
*/
|
|
25
|
+
const analyzeTypeProperties = (typeNode, tsType) => {
|
|
26
|
+
// Try TypeScript type checker first (more accurate)
|
|
27
|
+
if (tsType) {
|
|
28
|
+
const flags = tsType.getFlags();
|
|
29
|
+
// Check for string literal types (including unions like "a" | "b")
|
|
30
|
+
const isStringLiteral = (flags & ts.TypeFlags.StringLiteral) !== 0 ||
|
|
31
|
+
(tsType.isUnion() &&
|
|
32
|
+
tsType.types
|
|
33
|
+
.filter(t => (t.getFlags() & ts.TypeFlags.Undefined) === 0)
|
|
34
|
+
.every(t => (t.getFlags() & ts.TypeFlags.StringLiteral) !== 0) &&
|
|
35
|
+
tsType.types.some(t => (t.getFlags() & ts.TypeFlags.StringLiteral) !== 0));
|
|
36
|
+
if (isStringLiteral) {
|
|
37
|
+
let stringLiterals;
|
|
38
|
+
if (tsType.isUnion()) {
|
|
39
|
+
stringLiterals = tsType.types.filter(isStringLiteralType).map(t => t.value);
|
|
40
|
+
}
|
|
41
|
+
else if (isStringLiteralType(tsType)) {
|
|
42
|
+
stringLiterals = [tsType.value];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
stringLiterals = [];
|
|
46
|
+
}
|
|
47
|
+
return { type: "stringLiteral", stringLiterals };
|
|
48
|
+
}
|
|
49
|
+
// Check for enum types
|
|
50
|
+
if (isEnumLiteralType(tsType)) {
|
|
51
|
+
return { type: "enum", enumValues: [tsType.value] };
|
|
52
|
+
}
|
|
53
|
+
else if (tsType.isUnion()) {
|
|
54
|
+
const enumTypes = tsType.types.filter(isEnumLiteralType);
|
|
55
|
+
if (enumTypes.length > 0) {
|
|
56
|
+
const enumValues = enumTypes.map(t => t.value);
|
|
57
|
+
return { type: "enum", enumValues };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Check for union types that contain specific primitives (e.g., boolean | undefined)
|
|
61
|
+
if (tsType.isUnion()) {
|
|
62
|
+
const nonUndefinedTypes = tsType.types.filter(t => (t.getFlags() & ts.TypeFlags.Undefined) === 0);
|
|
63
|
+
// Check if all non-undefined types are booleans
|
|
64
|
+
if (nonUndefinedTypes.length > 0 &&
|
|
65
|
+
nonUndefinedTypes.every(t => (t.getFlags() & ts.TypeFlags.Boolean) !== 0 || (t.getFlags() & ts.TypeFlags.BooleanLiteral) !== 0)) {
|
|
66
|
+
return { type: "boolean" };
|
|
67
|
+
}
|
|
68
|
+
// Check if all non-undefined types are strings
|
|
69
|
+
if (nonUndefinedTypes.length > 0 && nonUndefinedTypes.every(t => (t.getFlags() & ts.TypeFlags.String) !== 0)) {
|
|
70
|
+
return { type: "string" };
|
|
71
|
+
}
|
|
72
|
+
// Check if all non-undefined types are numbers
|
|
73
|
+
if (nonUndefinedTypes.length > 0 &&
|
|
74
|
+
nonUndefinedTypes.every(t => (t.getFlags() & ts.TypeFlags.Number) !== 0 || (t.getFlags() & ts.TypeFlags.NumberLiteral) !== 0)) {
|
|
75
|
+
return { type: "number" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check for other primitive types
|
|
79
|
+
if ((flags & ts.TypeFlags.Boolean) !== 0 || (flags & ts.TypeFlags.BooleanLiteral) !== 0) {
|
|
80
|
+
return { type: "boolean" };
|
|
81
|
+
}
|
|
82
|
+
if ((flags & ts.TypeFlags.String) !== 0) {
|
|
83
|
+
return { type: "string" };
|
|
84
|
+
}
|
|
85
|
+
if ((flags & ts.TypeFlags.Number) !== 0 || (flags & ts.TypeFlags.NumberLiteral) !== 0) {
|
|
86
|
+
return { type: "number" };
|
|
87
|
+
}
|
|
88
|
+
if ((flags & ts.TypeFlags.Object) !== 0) {
|
|
89
|
+
return { type: "object" };
|
|
90
|
+
}
|
|
91
|
+
// If we couldn't identify the type, mark it as unknown
|
|
92
|
+
return { type: "unknown" };
|
|
93
|
+
}
|
|
94
|
+
// Fallback to AST-based analysis
|
|
95
|
+
if (!typeNode) {
|
|
96
|
+
return { type: "unknown" };
|
|
97
|
+
}
|
|
98
|
+
// Check for string literal types
|
|
99
|
+
const isStringLiteralAST = isTSLiteralTypeWithStringLiteral(typeNode) ||
|
|
100
|
+
(typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType && typeNode.types.every(isTSLiteralTypeWithStringLiteral));
|
|
101
|
+
if (isStringLiteralAST) {
|
|
102
|
+
let stringLiterals;
|
|
103
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
104
|
+
stringLiterals = typeNode.types.filter(isTSLiteralTypeWithStringLiteral).map(t => t.literal.value);
|
|
105
|
+
}
|
|
106
|
+
else if (isTSLiteralTypeWithStringLiteral(typeNode)) {
|
|
107
|
+
stringLiterals = [typeNode.literal.value];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
stringLiterals = [];
|
|
111
|
+
}
|
|
112
|
+
return { type: "stringLiteral", stringLiterals };
|
|
113
|
+
}
|
|
114
|
+
// Check for enum types (AST-based approximation)
|
|
115
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType &&
|
|
116
|
+
typeNode.types.every(t => t.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
117
|
+
t.literal.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
118
|
+
(typeof t.literal.value === "string" || typeof t.literal.value === "number"))) {
|
|
119
|
+
const enumValues = typeNode.types
|
|
120
|
+
.filter(isTSLiteralTypeWithLiteral)
|
|
121
|
+
.map(t => t.literal.value)
|
|
122
|
+
.filter((value) => typeof value === "string" || typeof value === "number");
|
|
123
|
+
return { type: "enum", enumValues };
|
|
124
|
+
}
|
|
125
|
+
// Check for other primitive types
|
|
126
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword ||
|
|
127
|
+
(typeNode.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
128
|
+
typeNode.literal.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
129
|
+
typeof typeNode.literal.value === "boolean")) {
|
|
130
|
+
return { type: "boolean" };
|
|
131
|
+
}
|
|
132
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSStringKeyword) {
|
|
133
|
+
return { type: "string" };
|
|
134
|
+
}
|
|
135
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSNumberKeyword ||
|
|
136
|
+
(typeNode.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
137
|
+
typeNode.literal.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
138
|
+
typeof typeNode.literal.value === "number")) {
|
|
139
|
+
return { type: "number" };
|
|
140
|
+
}
|
|
141
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral ||
|
|
142
|
+
typeNode.type === utils_1.AST_NODE_TYPES.TSArrayType ||
|
|
143
|
+
(typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
144
|
+
typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
145
|
+
/^(Array|Object|Record|Map|Set)$/.test(typeNode.typeName.name))) {
|
|
146
|
+
return { type: "object" };
|
|
147
|
+
}
|
|
148
|
+
return { type: "unknown" };
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Helper function to extract optional properties from a TypeScript type annotation
|
|
152
|
+
*/
|
|
153
|
+
const getOptionalPropsFromType = (context, typeNode) => {
|
|
154
|
+
if (!typeNode)
|
|
155
|
+
return [];
|
|
156
|
+
const optionalProps = [];
|
|
157
|
+
const processType = (node) => {
|
|
158
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference && node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
159
|
+
// Try to get type information using the type checker
|
|
160
|
+
const services = utils_1.ESLintUtils.getParserServices(context);
|
|
161
|
+
try {
|
|
162
|
+
const checker = services.program.getTypeChecker();
|
|
163
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
164
|
+
const type = checker.getTypeFromTypeNode(tsNode);
|
|
165
|
+
// Get the symbol properties - this works for resolved intersection types
|
|
166
|
+
const props = checker.getPropertiesOfType(type);
|
|
167
|
+
props.forEach(prop => {
|
|
168
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, tsNode);
|
|
169
|
+
// Check if the property is optional by examining all declarations
|
|
170
|
+
const isOptional = prop.declarations?.some(decl => "questionToken" in decl && decl.questionToken !== undefined) ?? false;
|
|
171
|
+
if (isOptional) {
|
|
172
|
+
const typeAnalysis = analyzeTypeProperties(undefined, propType);
|
|
173
|
+
optionalProps.push({
|
|
174
|
+
name: prop.getName(),
|
|
175
|
+
...typeAnalysis,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (_error) {
|
|
181
|
+
// If type checking fails, fall back to the original behavior
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
|
|
187
|
+
node.members.forEach(member => {
|
|
188
|
+
if (member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
|
|
189
|
+
member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
190
|
+
member.optional) {
|
|
191
|
+
const propName = member.key.name;
|
|
192
|
+
const propType = member.typeAnnotation?.typeAnnotation;
|
|
193
|
+
const typeAnalysis = analyzeTypeProperties(propType);
|
|
194
|
+
optionalProps.push({
|
|
195
|
+
name: propName,
|
|
196
|
+
...typeAnalysis,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
|
|
202
|
+
node.types.forEach(processType);
|
|
203
|
+
}
|
|
204
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
205
|
+
// For union types, we'll be conservative and process all types
|
|
206
|
+
node.types.forEach(processType);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
processType(typeNode);
|
|
210
|
+
return optionalProps;
|
|
211
|
+
};
|
|
212
|
+
exports.getOptionalPropsFromType = getOptionalPropsFromType;
|
|
213
|
+
//# sourceMappingURL=typescript-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript-utils.js","sourceRoot":"","sources":["../../../../../../../libs/eslint/plugin-trackunit/src/lib/utils/typescript-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { MappedOmit } from \"@trackunit/shared-utils\";\nimport { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport * as ts from \"typescript\";\n\n// Type guards for TypeScript API\ntype StringLiteralType = ts.Type & { value: string };\ntype EnumLiteralType = ts.Type & { value: string | number };\n\nfunction isStringLiteralType(type: ts.Type): type is StringLiteralType {\n return (type.flags & ts.TypeFlags.StringLiteral) !== 0 && \"value\" in type;\n}\n\nfunction isEnumLiteralType(type: ts.Type): type is EnumLiteralType {\n return (type.flags & ts.TypeFlags.EnumLiteral) !== 0 && \"value\" in type;\n}\n\n// Type guard for TSESTree literal nodes\nfunction isTSLiteralTypeWithStringLiteral(\n node: TSESTree.TypeNode\n): node is TSESTree.TSLiteralType & { literal: TSESTree.Literal & { value: string } } {\n return (\n node.type === AST_NODE_TYPES.TSLiteralType &&\n node.literal.type === AST_NODE_TYPES.Literal &&\n typeof node.literal.value === \"string\"\n );\n}\n\nfunction isTSLiteralTypeWithLiteral(\n node: TSESTree.TypeNode\n): node is TSESTree.TSLiteralType & { literal: TSESTree.Literal } {\n return node.type === AST_NODE_TYPES.TSLiteralType && node.literal.type === AST_NODE_TYPES.Literal;\n}\n\nexport type OptionalProp =\n | { name: string; type: \"boolean\" }\n | { name: string; type: \"string\" }\n | { name: string; type: \"number\" }\n | { name: string; type: \"object\" }\n | { name: string; type: \"stringLiteral\"; stringLiterals: Array<string> }\n | { name: string; type: \"enum\"; enumValues: Array<string | number> }\n | { name: string; type: \"unknown\" };\n\n/**\n * Unified helper function to analyze prop types\n */\nconst analyzeTypeProperties = (\n typeNode: TSESTree.TypeNode | undefined,\n tsType?: ts.Type\n): MappedOmit<OptionalProp, \"name\"> => {\n // Try TypeScript type checker first (more accurate)\n if (tsType) {\n const flags = tsType.getFlags();\n\n // Check for string literal types (including unions like \"a\" | \"b\")\n const isStringLiteral =\n (flags & ts.TypeFlags.StringLiteral) !== 0 ||\n (tsType.isUnion() &&\n tsType.types\n .filter(t => (t.getFlags() & ts.TypeFlags.Undefined) === 0)\n .every(t => (t.getFlags() & ts.TypeFlags.StringLiteral) !== 0) &&\n tsType.types.some(t => (t.getFlags() & ts.TypeFlags.StringLiteral) !== 0));\n\n if (isStringLiteral) {\n let stringLiterals: Array<string>;\n if (tsType.isUnion()) {\n stringLiterals = tsType.types.filter(isStringLiteralType).map(t => t.value);\n } else if (isStringLiteralType(tsType)) {\n stringLiterals = [tsType.value];\n } else {\n stringLiterals = [];\n }\n return { type: \"stringLiteral\", stringLiterals };\n }\n\n // Check for enum types\n if (isEnumLiteralType(tsType)) {\n return { type: \"enum\", enumValues: [tsType.value] };\n } else if (tsType.isUnion()) {\n const enumTypes = tsType.types.filter(isEnumLiteralType);\n if (enumTypes.length > 0) {\n const enumValues = enumTypes.map(t => t.value);\n return { type: \"enum\", enumValues };\n }\n }\n\n // Check for union types that contain specific primitives (e.g., boolean | undefined)\n if (tsType.isUnion()) {\n const nonUndefinedTypes = tsType.types.filter(t => (t.getFlags() & ts.TypeFlags.Undefined) === 0);\n\n // Check if all non-undefined types are booleans\n if (\n nonUndefinedTypes.length > 0 &&\n nonUndefinedTypes.every(\n t => (t.getFlags() & ts.TypeFlags.Boolean) !== 0 || (t.getFlags() & ts.TypeFlags.BooleanLiteral) !== 0\n )\n ) {\n return { type: \"boolean\" };\n }\n\n // Check if all non-undefined types are strings\n if (nonUndefinedTypes.length > 0 && nonUndefinedTypes.every(t => (t.getFlags() & ts.TypeFlags.String) !== 0)) {\n return { type: \"string\" };\n }\n\n // Check if all non-undefined types are numbers\n if (\n nonUndefinedTypes.length > 0 &&\n nonUndefinedTypes.every(\n t => (t.getFlags() & ts.TypeFlags.Number) !== 0 || (t.getFlags() & ts.TypeFlags.NumberLiteral) !== 0\n )\n ) {\n return { type: \"number\" };\n }\n }\n\n // Check for other primitive types\n if ((flags & ts.TypeFlags.Boolean) !== 0 || (flags & ts.TypeFlags.BooleanLiteral) !== 0) {\n return { type: \"boolean\" };\n }\n if ((flags & ts.TypeFlags.String) !== 0) {\n return { type: \"string\" };\n }\n if ((flags & ts.TypeFlags.Number) !== 0 || (flags & ts.TypeFlags.NumberLiteral) !== 0) {\n return { type: \"number\" };\n }\n if ((flags & ts.TypeFlags.Object) !== 0) {\n return { type: \"object\" };\n }\n\n // If we couldn't identify the type, mark it as unknown\n return { type: \"unknown\" };\n }\n\n // Fallback to AST-based analysis\n if (!typeNode) {\n return { type: \"unknown\" };\n }\n\n // Check for string literal types\n const isStringLiteralAST =\n isTSLiteralTypeWithStringLiteral(typeNode) ||\n (typeNode.type === AST_NODE_TYPES.TSUnionType && typeNode.types.every(isTSLiteralTypeWithStringLiteral));\n\n if (isStringLiteralAST) {\n let stringLiterals: Array<string>;\n if (typeNode.type === AST_NODE_TYPES.TSUnionType) {\n stringLiterals = typeNode.types.filter(isTSLiteralTypeWithStringLiteral).map(t => t.literal.value);\n } else if (isTSLiteralTypeWithStringLiteral(typeNode)) {\n stringLiterals = [typeNode.literal.value];\n } else {\n stringLiterals = [];\n }\n return { type: \"stringLiteral\", stringLiterals };\n }\n\n // Check for enum types (AST-based approximation)\n if (\n typeNode.type === AST_NODE_TYPES.TSUnionType &&\n typeNode.types.every(\n t =>\n t.type === AST_NODE_TYPES.TSLiteralType &&\n t.literal.type === AST_NODE_TYPES.Literal &&\n (typeof t.literal.value === \"string\" || typeof t.literal.value === \"number\")\n )\n ) {\n const enumValues = typeNode.types\n .filter(isTSLiteralTypeWithLiteral)\n .map(t => t.literal.value)\n .filter((value): value is string | number => typeof value === \"string\" || typeof value === \"number\");\n return { type: \"enum\", enumValues };\n }\n\n // Check for other primitive types\n if (\n typeNode.type === AST_NODE_TYPES.TSBooleanKeyword ||\n (typeNode.type === AST_NODE_TYPES.TSLiteralType &&\n typeNode.literal.type === AST_NODE_TYPES.Literal &&\n typeof typeNode.literal.value === \"boolean\")\n ) {\n return { type: \"boolean\" };\n }\n\n if (typeNode.type === AST_NODE_TYPES.TSStringKeyword) {\n return { type: \"string\" };\n }\n\n if (\n typeNode.type === AST_NODE_TYPES.TSNumberKeyword ||\n (typeNode.type === AST_NODE_TYPES.TSLiteralType &&\n typeNode.literal.type === AST_NODE_TYPES.Literal &&\n typeof typeNode.literal.value === \"number\")\n ) {\n return { type: \"number\" };\n }\n\n if (\n typeNode.type === AST_NODE_TYPES.TSTypeLiteral ||\n typeNode.type === AST_NODE_TYPES.TSArrayType ||\n (typeNode.type === AST_NODE_TYPES.TSTypeReference &&\n typeNode.typeName.type === AST_NODE_TYPES.Identifier &&\n /^(Array|Object|Record|Map|Set)$/.test(typeNode.typeName.name))\n ) {\n return { type: \"object\" };\n }\n\n return { type: \"unknown\" };\n};\n\n/**\n * Helper function to extract optional properties from a TypeScript type annotation\n */\nexport const getOptionalPropsFromType = (\n context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>,\n typeNode: TSESTree.TypeNode | undefined\n): Array<OptionalProp> => {\n if (!typeNode) return [];\n\n const optionalProps: Array<OptionalProp> = [];\n\n const processType = (node: TSESTree.TypeNode) => {\n if (node.type === AST_NODE_TYPES.TSTypeReference && node.typeName.type === AST_NODE_TYPES.Identifier) {\n // Try to get type information using the type checker\n const services = ESLintUtils.getParserServices(context);\n\n try {\n const checker = services.program.getTypeChecker();\n const tsNode = services.esTreeNodeToTSNodeMap.get(node);\n const type = checker.getTypeFromTypeNode(tsNode);\n\n // Get the symbol properties - this works for resolved intersection types\n const props = checker.getPropertiesOfType(type);\n props.forEach(prop => {\n const propType = checker.getTypeOfSymbolAtLocation(prop, tsNode);\n\n // Check if the property is optional by examining all declarations\n const isOptional =\n prop.declarations?.some(decl => \"questionToken\" in decl && decl.questionToken !== undefined) ?? false;\n\n if (isOptional) {\n const typeAnalysis = analyzeTypeProperties(undefined, propType);\n optionalProps.push({\n name: prop.getName(),\n ...typeAnalysis,\n });\n }\n });\n } catch (_error) {\n // If type checking fails, fall back to the original behavior\n return;\n }\n\n return;\n }\n\n if (node.type === AST_NODE_TYPES.TSTypeLiteral) {\n node.members.forEach(member => {\n if (\n member.type === AST_NODE_TYPES.TSPropertySignature &&\n member.key.type === AST_NODE_TYPES.Identifier &&\n member.optional\n ) {\n const propName = member.key.name;\n const propType = member.typeAnnotation?.typeAnnotation;\n\n const typeAnalysis = analyzeTypeProperties(propType);\n optionalProps.push({\n name: propName,\n ...typeAnalysis,\n });\n }\n });\n }\n\n if (node.type === AST_NODE_TYPES.TSIntersectionType) {\n node.types.forEach(processType);\n }\n\n if (node.type === AST_NODE_TYPES.TSUnionType) {\n // For union types, we'll be conservative and process all types\n node.types.forEach(processType);\n }\n };\n\n processType(typeNode);\n return optionalProps;\n};\n"]}
|