@ms-cloudpack/eslint-plugin 0.1.1

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.
Files changed (38) hide show
  1. package/README.md +113 -0
  2. package/lib/configs/recommended.d.ts +3 -0
  3. package/lib/configs/recommended.d.ts.map +1 -0
  4. package/lib/configs/recommended.js +10 -0
  5. package/lib/configs/recommended.js.map +1 -0
  6. package/lib/index.d.ts +10 -0
  7. package/lib/index.d.ts.map +1 -0
  8. package/lib/index.js +12 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/rules/no-unsupported-imports.d.ts +20 -0
  11. package/lib/rules/no-unsupported-imports.d.ts.map +1 -0
  12. package/lib/rules/no-unsupported-imports.js +179 -0
  13. package/lib/rules/no-unsupported-imports.js.map +1 -0
  14. package/lib/utils/PackageJson.d.ts +43 -0
  15. package/lib/utils/PackageJson.d.ts.map +1 -0
  16. package/lib/utils/PackageJson.js +4 -0
  17. package/lib/utils/PackageJson.js.map +1 -0
  18. package/lib/utils/getImportPathLiteralNode.d.ts +8 -0
  19. package/lib/utils/getImportPathLiteralNode.d.ts.map +1 -0
  20. package/lib/utils/getImportPathLiteralNode.js +32 -0
  21. package/lib/utils/getImportPathLiteralNode.js.map +1 -0
  22. package/lib/utils/getPackageInfo.d.ts +16 -0
  23. package/lib/utils/getPackageInfo.d.ts.map +1 -0
  24. package/lib/utils/getPackageInfo.js +57 -0
  25. package/lib/utils/getPackageInfo.js.map +1 -0
  26. package/lib/utils/parseImportIfRelevant.d.ts +19 -0
  27. package/lib/utils/parseImportIfRelevant.d.ts.map +1 -0
  28. package/lib/utils/parseImportIfRelevant.js +40 -0
  29. package/lib/utils/parseImportIfRelevant.js.map +1 -0
  30. package/lib/utils/pathSatisfiesAnyExport.d.ts +8 -0
  31. package/lib/utils/pathSatisfiesAnyExport.d.ts.map +1 -0
  32. package/lib/utils/pathSatisfiesAnyExport.js +38 -0
  33. package/lib/utils/pathSatisfiesAnyExport.js.map +1 -0
  34. package/lib/utils/resolvePackageRoot.d.ts +7 -0
  35. package/lib/utils/resolvePackageRoot.d.ts.map +1 -0
  36. package/lib/utils/resolvePackageRoot.js +49 -0
  37. package/lib/utils/resolvePackageRoot.js.map +1 -0
  38. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @ms-cloudpack/eslint-plugin
2
+
3
+ `@ms-cloudpack/eslint-plugin` provides a shared config and custom rules to help encourage Cloudpack-friendly coding practices, specifically regarding imports and exports.
4
+
5
+ ## Recommended config
6
+
7
+ To enable the `@ms-cloudpack/recommended` configuration, add it to your ESLint config file:
8
+
9
+ ```json
10
+ {
11
+ "plugins": ["@ms-cloudpack"],
12
+ "extends": ["plugin:@ms-cloudpack/recommended"]
13
+ }
14
+ ```
15
+
16
+ `@ms-cloudpack/recommended` currently includes the following rules:
17
+
18
+ - `@ms-cloudpack/no-unsupported-imports`
19
+
20
+ ## Included rules
21
+
22
+ - ✓: Enabled with `@ms-cloudpack/recommended`
23
+ - 🔧: Fixable with `--fix`
24
+
25
+ | ✓ | 🔧 | Rule | Description |
26
+ | :-: | :-: | :------------------------------------- | :-------------------------------------------------------- |
27
+ | ✓ | | `@ms-cloudpack/no-unsupported-imports` | Ban deep imports that are not defined in an `exports` map |
28
+
29
+ ### `no-unsupported-imports`
30
+
31
+ This rule bans deep imports from paths which aren't defined in a package's [exports map](https://nodejs.org/api/packages.html#package-entry-points). It also bans **all** deep imports from packages which don't define exports maps.
32
+
33
+ Features:
34
+
35
+ - Supports all common "import-like" syntax variants (`import`, `export` from paths, `require`)
36
+ - Basic support for wildcards and directories in exports maps
37
+ - Caches resolved package info to reduce disk access
38
+ - Provides suggestions (not auto-fixes) for importing from package root in some cases
39
+
40
+ Current limitations (handling for some of these could be added if needed):
41
+
42
+ - Not sensitive to conditions: it passes if a path is exported for any condition
43
+ - Can't auto-fix. This would require either type information (to figure out where symbols are defined and whether they're exported from the root) or complete manual traversal of a package's exported paths and symbols.
44
+ - Limited support for later exclusions underneath wildcards or directories
45
+ - Doesn't verify that the resolved path exists on disk (reduces cost and complexity)
46
+ - Does no verification of top-level package imports. This is intentional for performance and usually correct, but it would fail to detect a package that _only_ defines deep imports in its exports map. (Importing specific packages with this issue could be banned with ESLint's `no-restricted-imports`.)
47
+
48
+ #### Options
49
+
50
+ - `ignorePatterns` (`string[]`): Don't check imports matching these regular expression patterns. Examples:
51
+ - `"^foo/"`: Ignore all imports under `foo`
52
+ - `"^foo/bar/`: Ignore imports under `foo/bar`
53
+ - `"^foo/bar/baz$"`: Ignore the specific path `foo/bar/baz`
54
+
55
+ #### Examples
56
+
57
+ For this package without an exports map:
58
+
59
+ ```json
60
+ {
61
+ "name": "no-exports",
62
+ "main": "lib/index.js"
63
+ }
64
+ ```
65
+
66
+ The rule bans all deep imports, regardless of whether the files being imported exist on disk:
67
+
68
+ ```js
69
+ // ✅ OK
70
+ import { foo } from 'no-exports';
71
+ // and other variants of import/export syntax
72
+
73
+ // ❌ Error
74
+ import { foo } from 'no-exports/lib/foo';
75
+ import { foo } from 'no-exports/lib/index';
76
+ const packageJson = require('no-exports/package.json');
77
+
78
+ // ✅ OK with ignorePatterns such as ["^no-exports/"] or ["^no-exports/lib/foo$"]
79
+ import { foo } from 'no-exports/lib/foo';
80
+ ```
81
+
82
+ For this package with an exports map:
83
+
84
+ ```json
85
+ {
86
+ "name": "with-exports",
87
+ "main": "lib/index.js",
88
+ "exports": {
89
+ ".": "./lib/index.js",
90
+ "./lib/foo": "./lib/foo.js",
91
+ "./lib/utils/*": "./lib/utils/*.js"
92
+ }
93
+ }
94
+ ```
95
+
96
+ The rule allows root imports or those matching keys in the exports map:
97
+
98
+ ```js
99
+ // ✅ OK
100
+ import { foo } from 'with-exports';
101
+ import { foo } from 'with-exports/lib/foo';
102
+ import { foo } from 'with-exports/lib/utils/bar';
103
+
104
+ // ❌ Error
105
+ // Not included in exports
106
+ import { foo } from 'with-exports/lib/nope';
107
+ const packageJson = require('with-exports/package.json');
108
+ // Path in exports doesn't have extension
109
+ import { foo } from 'with-exports/lib/foo.js';
110
+
111
+ // ✅ OK with ignorePatterns such as ["^with-exports/"] or ["^with-exports/lib/nope$"]
112
+ import { foo } from 'with-exports/lib/nope';
113
+ ```
@@ -0,0 +1,3 @@
1
+ import type { ESLint } from 'eslint';
2
+ export declare const recommended: ESLint.ConfigData;
3
+ //# sourceMappingURL=recommended.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.d.ts","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,UAKhC,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recommended = void 0;
4
+ exports.recommended = {
5
+ plugins: ['@ms-cloudpack', '@typescript-eslint'],
6
+ rules: {
7
+ '@ms-cloudpack/no-unsupported-imports': 'error',
8
+ },
9
+ };
10
+ //# sourceMappingURL=recommended.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommended.js","sourceRoot":"","sources":["../../src/configs/recommended.ts"],"names":[],"mappings":";;;AAEa,QAAA,WAAW,GAAsB;IAC5C,OAAO,EAAE,CAAC,eAAe,EAAE,oBAAoB,CAAC;IAChD,KAAK,EAAE;QACL,sCAAsC,EAAE,OAAO;KAChD;CACF,CAAC","sourcesContent":["import type { ESLint } from 'eslint';\n\nexport const recommended: ESLint.ConfigData = {\n plugins: ['@ms-cloudpack', '@typescript-eslint'],\n rules: {\n '@ms-cloudpack/no-unsupported-imports': 'error',\n },\n};\n"]}
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ declare const _default: {
2
+ configs: {
3
+ recommended: import("eslint").ESLint.ConfigData<import("eslint").Linter.RulesRecord>;
4
+ };
5
+ rules: {
6
+ 'no-unsupported-imports': import("@typescript-eslint/utils/dist/ts-eslint/Rule.js").RuleModule<import("./rules/no-unsupported-imports.js").MessageIds, import("./rules/no-unsupported-imports.js").RawRuleOptions[], import("@typescript-eslint/utils/dist/ts-eslint/Rule.js").RuleListener>;
7
+ };
8
+ };
9
+ export = _default;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;AAGA,kBAOE"}
package/lib/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ const recommended_js_1 = require("./configs/recommended.js");
3
+ const no_unsupported_imports_js_1 = require("./rules/no-unsupported-imports.js");
4
+ module.exports = {
5
+ configs: {
6
+ recommended: recommended_js_1.recommended,
7
+ },
8
+ rules: {
9
+ 'no-unsupported-imports': no_unsupported_imports_js_1.rule,
10
+ },
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,6DAAuD;AACvD,iFAAiF;AAEjF,iBAAS;IACP,OAAO,EAAE;QACP,WAAW,EAAX,4BAAW;KACZ;IACD,KAAK,EAAE;QACL,wBAAwB,EAAE,gCAAoB;KAC/C;CACF,CAAC","sourcesContent":["import { recommended } from './configs/recommended.js';\nimport { rule as noUnsupportedImports } from './rules/no-unsupported-imports.js';\n\nexport = {\n configs: {\n recommended,\n },\n rules: {\n 'no-unsupported-imports': noUnsupportedImports,\n },\n};\n"]}
@@ -0,0 +1,20 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ /** Processed rule options */
3
+ type RuleOptions = {
4
+ ignorePatterns: RegExp[];
5
+ debug: boolean;
6
+ };
7
+ /** Rule options as specified in the config */
8
+ export type RawRuleOptions = Omit<Partial<RuleOptions>, 'ignorePatterns'> & {
9
+ ignorePatterns?: string[];
10
+ };
11
+ export type ErrorMessageIds = 'noExports' | 'noExportsLocal' | 'notExported' | 'notExportedLocal';
12
+ export type SuggestMessageIds = 'useTopLevel';
13
+ export type MessageIds = ErrorMessageIds | SuggestMessageIds;
14
+ export type ErrorData = {
15
+ packageName: string;
16
+ subPath: string;
17
+ };
18
+ export declare const rule: TSESLint.RuleModule<MessageIds, RawRuleOptions[]>;
19
+ export {};
20
+ //# sourceMappingURL=no-unsupported-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unsupported-imports.d.ts","sourceRoot":"","sources":["../../src/rules/no-unsupported-imports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAOzD,6BAA6B;AAC7B,KAAK,WAAW,GAAG;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC,GAAG;IAC1E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAOF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,gBAAgB,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAClG,MAAM,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAC9C,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA+BF,eAAO,MAAM,IAAI,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,cAAc,EAAE,CA2DlE,CAAC"}
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = void 0;
4
+ const getImportPathLiteralNode_1 = require("../utils/getImportPathLiteralNode");
5
+ const getPackageInfo_1 = require("../utils/getPackageInfo");
6
+ const parseImportIfRelevant_1 = require("../utils/parseImportIfRelevant");
7
+ const pathSatisfiesAnyExport_1 = require("../utils/pathSatisfiesAnyExport");
8
+ const resolvePackageRoot_1 = require("../utils/resolvePackageRoot");
9
+ const defaultOptions = {
10
+ ignorePatterns: [],
11
+ debug: false,
12
+ };
13
+ const preferTopLevel = 'prefer importing directly from "{{packageName}}" if possible';
14
+ const noExports = '"{{packageName}}" doesn\'t have an exports map, so deep imports aren\'t allowed; ' + preferTopLevel;
15
+ const notExported = 'Path "{{subPath}}" is not exported by "{{packageName}}", according to its exports map; ' + preferTopLevel;
16
+ const localMessage = '(Consider updating "{{packageName}}" to export additional identifiers if needed, ' +
17
+ 'or if deep imports are strictly necessary, add an exports map.)';
18
+ const messages = {
19
+ noExports,
20
+ noExportsLocal: `${noExports}. ${localMessage}`,
21
+ notExported,
22
+ notExportedLocal: `${notExported}. ${localMessage}`,
23
+ useTopLevel: 'Import directly from "{{packageName}}", if possible',
24
+ };
25
+ const suggestMessageId = 'useTopLevel';
26
+ exports.rule = {
27
+ defaultOptions: [defaultOptions],
28
+ meta: {
29
+ type: 'problem',
30
+ docs: {
31
+ description: 'ban importing from non-exported paths',
32
+ recommended: 'error',
33
+ },
34
+ hasSuggestions: true,
35
+ messages,
36
+ schema: [
37
+ {
38
+ type: 'object',
39
+ properties: {
40
+ ignorePatterns: {
41
+ type: 'array',
42
+ description: 'Ignore imports matching these regular expression patterns.',
43
+ items: { type: 'string' },
44
+ },
45
+ debug: { type: 'boolean' },
46
+ },
47
+ additionalProperties: false,
48
+ },
49
+ ],
50
+ },
51
+ create: (context) => {
52
+ const options = context.options?.[0] || {};
53
+ const ruleContext = {
54
+ ...context,
55
+ options: {
56
+ debug: false,
57
+ ...options,
58
+ ignorePatterns: (options.ignorePatterns || []).map((str) => new RegExp(str)),
59
+ },
60
+ filename: context.getFilename(),
61
+ };
62
+ return {
63
+ // import foo from 'foo'
64
+ // import * as foo from 'foo'
65
+ // import { foo } from 'foo'
66
+ // import { foo as bar } from 'foo'
67
+ // import 'foo'
68
+ ImportDeclaration: (node) => checkImportOrExport(ruleContext, node),
69
+ // await import('foo')
70
+ ImportExpression: (node) => checkImportOrExport(ruleContext, node),
71
+ // import foo = require('foo')
72
+ TSImportEqualsDeclaration: (node) => checkImportOrExport(ruleContext, node),
73
+ // export { foo } from 'foo'
74
+ // export { foo as bar } from 'foo'
75
+ ExportNamedDeclaration: (node) => checkImportOrExport(ruleContext, node),
76
+ // export * from 'foo'
77
+ // export * as foo from 'foo'
78
+ ExportAllDeclaration: (node) => checkImportOrExport(ruleContext, node),
79
+ // require('foo')
80
+ // require.resolve('foo')
81
+ CallExpression: (node) => checkImportOrExport(ruleContext, node),
82
+ };
83
+ },
84
+ };
85
+ function checkImportOrExport(context, node) {
86
+ const pathNode = (0, getImportPathLiteralNode_1.getImportPathLiteralNode)(node);
87
+ if (!pathNode) {
88
+ return;
89
+ }
90
+ const errorData = checkImportPath(context, pathNode.value);
91
+ if (!errorData) {
92
+ return; // Import is fine
93
+ }
94
+ // At this point, the import is probably invalid, so report it.
95
+ // (Might still be a false positive if it's a local package that hasn't been built yet.)
96
+ const { messageId, ...data } = errorData;
97
+ // For named imports/exports or import *, suggest importing from the package root.
98
+ // This is just a suggestion, so it doesn't have to be correct, but we also want to avoid
99
+ // suggesting when it's likely to be misleading or might cause issues; for example:
100
+ // - Default imports may refer to an actual default export or the entire module
101
+ // - Side effect imports are likely specific to a particular file
102
+ // - Async import()
103
+ // - export * from an entire package might export a lot more things
104
+ // - require() could be used in many ways (would have to look at more context)
105
+ // - require.resolve() is typically used to find the path to a specific file
106
+ let suggestion;
107
+ if (
108
+ // import { foo } from 'foo/bar'
109
+ (node.type === 'ImportDeclaration' && node.specifiers[0]?.type === 'ImportSpecifier') ||
110
+ // import * as foo from 'foo/bar'
111
+ (node.type === 'ImportDeclaration' && node.specifiers[0]?.type === 'ImportNamespaceSpecifier') ||
112
+ // import foo = require('foo/bar')
113
+ node.type === 'TSImportEqualsDeclaration' ||
114
+ // export { foo } from 'foo/bar'
115
+ node.type === 'ExportNamedDeclaration') {
116
+ suggestion = {
117
+ messageId: suggestMessageId,
118
+ data,
119
+ fix: (fixer) => fixer.replaceTextRange(
120
+ // Keep the quotes
121
+ [pathNode.range[0] + 1, pathNode.range[1] - 1], data.packageName),
122
+ };
123
+ }
124
+ context.report({
125
+ node: pathNode,
126
+ messageId,
127
+ data,
128
+ suggest: suggestion ? [suggestion] : undefined,
129
+ });
130
+ }
131
+ function checkImportPath(context, importPath) {
132
+ const { ignorePatterns } = context.options;
133
+ const importParts = (0, parseImportIfRelevant_1.parseImportIfRelevant)(importPath, ignorePatterns);
134
+ if (!importParts) {
135
+ // Import is relative, top-level, built-in, or ignored
136
+ return null;
137
+ }
138
+ const { packageName, subPath } = importParts;
139
+ // Find the root of the current file's package and read its package.json
140
+ const currentPkg = (0, getPackageInfo_1.getPackageInfo)(context.filename, context.options.debug);
141
+ if (!currentPkg || currentPkg.json.name === packageName) {
142
+ // - If package.json for the current file's package wasn't found, ignore the import
143
+ // (getPackageInfo already logged a debug message about it)
144
+ // - If the import is a self-reference, ignore it
145
+ return null;
146
+ }
147
+ // Resolve packageName's top-level import so we can find the package.json.
148
+ // (This resolution must be done in the context of the current package in case eslint is running
149
+ // in a different directory, e.g. monorepo root, where the package may not be installed or a
150
+ // different version may be installed.)
151
+ const resolvedPkgRoot = (0, resolvePackageRoot_1.resolvePackageRoot)(currentPkg.path, packageName, context.options.debug);
152
+ if (!resolvedPkgRoot) {
153
+ // Maybe an optional dep?
154
+ return null;
155
+ }
156
+ // Read packageName's package.json
157
+ const importPkg = (0, getPackageInfo_1.getPackageInfo)(resolvedPkgRoot, context.options.debug);
158
+ if (!importPkg) {
159
+ return null;
160
+ }
161
+ if (!importPkg.json.exports) {
162
+ // Deep imports are always an error if there's no exports map
163
+ return {
164
+ messageId: importPkg.isLocal ? 'noExportsLocal' : 'noExports',
165
+ subPath: subPath,
166
+ packageName,
167
+ };
168
+ }
169
+ // Check if subPath matches any paths from exports (disregarding conditions)
170
+ if (!(0, pathSatisfiesAnyExport_1.pathSatisfiesAnyExport)(importPkg.json.exports, subPath)) {
171
+ return {
172
+ messageId: importPkg.isLocal ? 'notExportedLocal' : 'notExported',
173
+ subPath: subPath,
174
+ packageName,
175
+ };
176
+ }
177
+ return null;
178
+ }
179
+ //# sourceMappingURL=no-unsupported-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unsupported-imports.js","sourceRoot":"","sources":["../../src/rules/no-unsupported-imports.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\nimport { getImportPathLiteralNode, type ImportLikeNode } from '../utils/getImportPathLiteralNode';\nimport { getPackageInfo } from '../utils/getPackageInfo';\nimport { parseImportIfRelevant } from '../utils/parseImportIfRelevant';\nimport { pathSatisfiesAnyExport } from '../utils/pathSatisfiesAnyExport';\nimport { resolvePackageRoot } from '../utils/resolvePackageRoot';\n\n/** Processed rule options */\ntype RuleOptions = {\n ignorePatterns: RegExp[];\n debug: boolean;\n};\n\n/** Rule options as specified in the config */\nexport type RawRuleOptions = Omit<Partial<RuleOptions>, 'ignorePatterns'> & {\n ignorePatterns?: string[];\n};\n\ntype RuleContext = Omit<RawRuleContext, 'options'> & {\n options: RuleOptions;\n filename: string;\n};\n\nexport type ErrorMessageIds = 'noExports' | 'noExportsLocal' | 'notExported' | 'notExportedLocal';\nexport type SuggestMessageIds = 'useTopLevel';\nexport type MessageIds = ErrorMessageIds | SuggestMessageIds;\n\nexport type ErrorData = {\n packageName: string;\n subPath: string;\n};\n\ntype ErrorResult = ErrorData & {\n messageId: ErrorMessageIds;\n};\n\ntype RawRuleContext = TSESLint.RuleContext<MessageIds, RawRuleOptions[]>;\n\nconst defaultOptions: RawRuleOptions = {\n ignorePatterns: [],\n debug: false,\n};\n\nconst preferTopLevel = 'prefer importing directly from \"{{packageName}}\" if possible';\nconst noExports = '\"{{packageName}}\" doesn\\'t have an exports map, so deep imports aren\\'t allowed; ' + preferTopLevel;\nconst notExported =\n 'Path \"{{subPath}}\" is not exported by \"{{packageName}}\", according to its exports map; ' + preferTopLevel;\nconst localMessage =\n '(Consider updating \"{{packageName}}\" to export additional identifiers if needed, ' +\n 'or if deep imports are strictly necessary, add an exports map.)';\n\nconst messages: Record<MessageIds, string> = {\n noExports,\n noExportsLocal: `${noExports}. ${localMessage}`,\n notExported,\n notExportedLocal: `${notExported}. ${localMessage}`,\n useTopLevel: 'Import directly from \"{{packageName}}\", if possible',\n};\n\nconst suggestMessageId: SuggestMessageIds = 'useTopLevel';\n\nexport const rule: TSESLint.RuleModule<MessageIds, RawRuleOptions[]> = {\n defaultOptions: [defaultOptions],\n meta: {\n type: 'problem',\n docs: {\n description: 'ban importing from non-exported paths',\n recommended: 'error',\n },\n hasSuggestions: true,\n messages,\n schema: [\n {\n type: 'object',\n properties: {\n ignorePatterns: {\n type: 'array',\n description: 'Ignore imports matching these regular expression patterns.',\n items: { type: 'string' },\n },\n debug: { type: 'boolean' },\n },\n additionalProperties: false,\n },\n ],\n },\n create: (context) => {\n const options = context.options?.[0] || {};\n const ruleContext: RuleContext = {\n ...context,\n options: {\n debug: false,\n ...options,\n ignorePatterns: (options.ignorePatterns || []).map((str) => new RegExp(str)),\n },\n filename: context.getFilename(),\n };\n\n return {\n // import foo from 'foo'\n // import * as foo from 'foo'\n // import { foo } from 'foo'\n // import { foo as bar } from 'foo'\n // import 'foo'\n ImportDeclaration: (node) => checkImportOrExport(ruleContext, node),\n // await import('foo')\n ImportExpression: (node) => checkImportOrExport(ruleContext, node),\n // import foo = require('foo')\n TSImportEqualsDeclaration: (node) => checkImportOrExport(ruleContext, node),\n // export { foo } from 'foo'\n // export { foo as bar } from 'foo'\n ExportNamedDeclaration: (node) => checkImportOrExport(ruleContext, node),\n // export * from 'foo'\n // export * as foo from 'foo'\n ExportAllDeclaration: (node) => checkImportOrExport(ruleContext, node),\n // require('foo')\n // require.resolve('foo')\n CallExpression: (node) => checkImportOrExport(ruleContext, node),\n };\n },\n};\n\nfunction checkImportOrExport(context: RuleContext, node: ImportLikeNode) {\n const pathNode = getImportPathLiteralNode(node);\n if (!pathNode) {\n return;\n }\n\n const errorData = checkImportPath(context, pathNode.value);\n if (!errorData) {\n return; // Import is fine\n }\n\n // At this point, the import is probably invalid, so report it.\n // (Might still be a false positive if it's a local package that hasn't been built yet.)\n const { messageId, ...data } = errorData;\n\n // For named imports/exports or import *, suggest importing from the package root.\n // This is just a suggestion, so it doesn't have to be correct, but we also want to avoid\n // suggesting when it's likely to be misleading or might cause issues; for example:\n // - Default imports may refer to an actual default export or the entire module\n // - Side effect imports are likely specific to a particular file\n // - Async import()\n // - export * from an entire package might export a lot more things\n // - require() could be used in many ways (would have to look at more context)\n // - require.resolve() is typically used to find the path to a specific file\n let suggestion: TSESLint.ReportSuggestionArray<SuggestMessageIds>[number] | undefined;\n if (\n // import { foo } from 'foo/bar'\n (node.type === 'ImportDeclaration' && node.specifiers[0]?.type === 'ImportSpecifier') ||\n // import * as foo from 'foo/bar'\n (node.type === 'ImportDeclaration' && node.specifiers[0]?.type === 'ImportNamespaceSpecifier') ||\n // import foo = require('foo/bar')\n node.type === 'TSImportEqualsDeclaration' ||\n // export { foo } from 'foo/bar'\n node.type === 'ExportNamedDeclaration'\n ) {\n suggestion = {\n messageId: suggestMessageId,\n data,\n fix: (fixer) =>\n fixer.replaceTextRange(\n // Keep the quotes\n [pathNode.range[0] + 1, pathNode.range[1] - 1],\n data.packageName,\n ),\n };\n }\n\n context.report({\n node: pathNode,\n messageId,\n data,\n suggest: suggestion ? [suggestion] : undefined,\n });\n}\n\nfunction checkImportPath(context: RuleContext, importPath: string): ErrorResult | null {\n const { ignorePatterns } = context.options;\n\n const importParts = parseImportIfRelevant(importPath, ignorePatterns);\n if (!importParts) {\n // Import is relative, top-level, built-in, or ignored\n return null;\n }\n const { packageName, subPath } = importParts;\n\n // Find the root of the current file's package and read its package.json\n const currentPkg = getPackageInfo(context.filename, context.options.debug);\n if (!currentPkg || currentPkg.json.name === packageName) {\n // - If package.json for the current file's package wasn't found, ignore the import\n // (getPackageInfo already logged a debug message about it)\n // - If the import is a self-reference, ignore it\n return null;\n }\n\n // Resolve packageName's top-level import so we can find the package.json.\n // (This resolution must be done in the context of the current package in case eslint is running\n // in a different directory, e.g. monorepo root, where the package may not be installed or a\n // different version may be installed.)\n const resolvedPkgRoot = resolvePackageRoot(currentPkg.path, packageName, context.options.debug);\n if (!resolvedPkgRoot) {\n // Maybe an optional dep?\n return null;\n }\n\n // Read packageName's package.json\n const importPkg = getPackageInfo(resolvedPkgRoot, context.options.debug);\n if (!importPkg) {\n return null;\n }\n\n if (!importPkg.json.exports) {\n // Deep imports are always an error if there's no exports map\n return {\n messageId: importPkg.isLocal ? 'noExportsLocal' : 'noExports',\n subPath: subPath,\n packageName,\n };\n }\n\n // Check if subPath matches any paths from exports (disregarding conditions)\n if (!pathSatisfiesAnyExport(importPkg.json.exports, subPath)) {\n return {\n messageId: importPkg.isLocal ? 'notExportedLocal' : 'notExported',\n subPath: subPath,\n packageName,\n };\n }\n return null;\n}\n"]}
@@ -0,0 +1,43 @@
1
+ type PathLiteral = '.' | `./${string}`;
2
+ /**
3
+ * The module path that is resolved when this specifier is imported.
4
+ * Set to `null` to disallow importing this module.
5
+ */
6
+ type PackageExportsEntryPath = PathLiteral | null;
7
+ type PackageExportsEntry = PackageExportsEntryPath | PackageExportsConditionsObject;
8
+ /**
9
+ * Used to allow fallbacks in case this environment doesn't support the preceding entries.
10
+ * Fallbacks are not supported by all implementations.
11
+ */
12
+ type PackageExportsFallback = PackageExportsEntry[];
13
+ type PackageExportsEntryOrFallback = PackageExportsEntry | PackageExportsFallback;
14
+ /** Used to specify conditional exports */
15
+ type PackageExportsConditionsObject = {
16
+ [condition: string]: PackageExportsEntryOrFallback;
17
+ };
18
+ /**
19
+ * Each key is the module path that is resolved when the module specifier starts with "name/".
20
+ * Special case: `"."` defines the primary entry point (shadows the `main` field).
21
+ * The key `"./*"` allows external modules to import any subpath (not recommended).
22
+ */
23
+ type PackageExportsPathsObject = {
24
+ [path in PathLiteral]?: PackageExportsEntryOrFallback;
25
+ };
26
+ /**
27
+ * The `exports` field allows defining the entry points of a package.
28
+ * - If a single string: load this module when the package name is imported
29
+ * (shadows the `main` field)
30
+ * - If an array: load the first one that exists (fallbacks are not supported by all implementations)
31
+ * - If an object where *all* keys start with `.`: see `PackageExportsPathsObject`
32
+ * - If an object where *no* keys start with `.`: see `PackageExportsConditionsObject`
33
+ * - Objects mixing paths and conditions are not valid
34
+ */
35
+ export type PackageExports = PackageExportsEntryPath | PackageExportsPathsObject | PackageExportsConditionsObject | PackageExportsFallback;
36
+ /** Relevant package.json contents for checking the exports map */
37
+ export type PackageJson = {
38
+ name: string;
39
+ exports?: PackageExports;
40
+ [key: string]: unknown;
41
+ };
42
+ export {};
43
+ //# sourceMappingURL=PackageJson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageJson.d.ts","sourceRoot":"","sources":["../../src/utils/PackageJson.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,GAAG,GAAG,KAAK,MAAM,EAAE,CAAC;AAEvC;;;GAGG;AACH,KAAK,uBAAuB,GAAG,WAAW,GAAG,IAAI,CAAC;AAElD,KAAK,mBAAmB,GAAG,uBAAuB,GAAG,8BAA8B,CAAC;AAEpF;;;GAGG;AACH,KAAK,sBAAsB,GAAG,mBAAmB,EAAE,CAAC;AAEpD,KAAK,6BAA6B,GAAG,mBAAmB,GAAG,sBAAsB,CAAC;AAElF,0CAA0C;AAC1C,KAAK,8BAA8B,GAAG;IACpC,CAAC,SAAS,EAAE,MAAM,GAAG,6BAA6B,CAAC;CACpD,CAAC;AAEF;;;;GAIG;AACH,KAAK,yBAAyB,GAAG;KAC9B,IAAI,IAAI,WAAW,CAAC,CAAC,EAAE,6BAA6B;CACtD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACtB,uBAAuB,GACvB,yBAAyB,GACzB,8BAA8B,GAC9B,sBAAsB,CAAC;AAG3B,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC"}
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // These types are more precisely based on the schema and should replace the ones in bundler-types in another PR
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=PackageJson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageJson.js","sourceRoot":"","sources":["../../src/utils/PackageJson.ts"],"names":[],"mappings":";AAAA,gHAAgH","sourcesContent":["// These types are more precisely based on the schema and should replace the ones in bundler-types in another PR\n\ntype PathLiteral = '.' | `./${string}`;\n\n/**\n * The module path that is resolved when this specifier is imported.\n * Set to `null` to disallow importing this module.\n */\ntype PackageExportsEntryPath = PathLiteral | null;\n\ntype PackageExportsEntry = PackageExportsEntryPath | PackageExportsConditionsObject;\n\n/**\n * Used to allow fallbacks in case this environment doesn't support the preceding entries.\n * Fallbacks are not supported by all implementations.\n */\ntype PackageExportsFallback = PackageExportsEntry[];\n\ntype PackageExportsEntryOrFallback = PackageExportsEntry | PackageExportsFallback;\n\n/** Used to specify conditional exports */\ntype PackageExportsConditionsObject = {\n [condition: string]: PackageExportsEntryOrFallback;\n};\n\n/**\n * Each key is the module path that is resolved when the module specifier starts with \"name/\".\n * Special case: `\".\"` defines the primary entry point (shadows the `main` field).\n * The key `\"./*\"` allows external modules to import any subpath (not recommended).\n */\ntype PackageExportsPathsObject = {\n [path in PathLiteral]?: PackageExportsEntryOrFallback;\n};\n\n/**\n * The `exports` field allows defining the entry points of a package.\n * - If a single string: load this module when the package name is imported\n * (shadows the `main` field)\n * - If an array: load the first one that exists (fallbacks are not supported by all implementations)\n * - If an object where *all* keys start with `.`: see `PackageExportsPathsObject`\n * - If an object where *no* keys start with `.`: see `PackageExportsConditionsObject`\n * - Objects mixing paths and conditions are not valid\n */\nexport type PackageExports =\n | PackageExportsEntryPath\n | PackageExportsPathsObject\n | PackageExportsConditionsObject\n | PackageExportsFallback;\n\n// this is a basic type which should not be copied elsewhere\n/** Relevant package.json contents for checking the exports map */\nexport type PackageJson = {\n name: string;\n exports?: PackageExports;\n [key: string]: unknown;\n};\n"]}
@@ -0,0 +1,8 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ export type ImportLikeNode = TSESTree.CallExpression | TSESTree.ExportAllDeclaration | TSESTree.ExportNamedDeclaration | TSESTree.ImportDeclaration | TSESTree.ImportExpression | TSESTree.TSImportEqualsDeclaration;
3
+ /**
4
+ * Verifies that the given node contains a string literal import path, and if so, returns the path node.
5
+ * (For `CallExpression` nodes, also verifies that the callee is `require` or `require.resolve`.)
6
+ */
7
+ export declare function getImportPathLiteralNode(node: ImportLikeNode): TSESTree.StringLiteral | null;
8
+ //# sourceMappingURL=getImportPathLiteralNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getImportPathLiteralNode.d.ts","sourceRoot":"","sources":["../../src/utils/getImportPathLiteralNode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,MAAM,MAAM,cAAc,GACtB,QAAQ,CAAC,cAAc,GACvB,QAAQ,CAAC,oBAAoB,GAC7B,QAAQ,CAAC,sBAAsB,GAC/B,QAAQ,CAAC,iBAAiB,GAC1B,QAAQ,CAAC,gBAAgB,GACzB,QAAQ,CAAC,yBAAyB,CAAC;AAEvC;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,CAAC,aAAa,GAAG,IAAI,CAwB5F"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getImportPathLiteralNode = void 0;
4
+ /**
5
+ * Verifies that the given node contains a string literal import path, and if so, returns the path node.
6
+ * (For `CallExpression` nodes, also verifies that the callee is `require` or `require.resolve`.)
7
+ */
8
+ function getImportPathLiteralNode(node) {
9
+ let pathNode = null;
10
+ if (node.type === 'CallExpression') {
11
+ const isRequire = node.callee.type === 'Identifier' && node.callee.name === 'require';
12
+ const isRequireResolve = node.callee.type === 'MemberExpression' &&
13
+ node.callee.object.type === 'Identifier' &&
14
+ node.callee.object.name === 'require' &&
15
+ node.callee.property.type === 'Identifier' &&
16
+ node.callee.property.name === 'resolve';
17
+ if ((isRequire || isRequireResolve) && node.arguments.length === 1) {
18
+ pathNode = node.arguments[0];
19
+ }
20
+ }
21
+ else if (node.type === 'TSImportEqualsDeclaration') {
22
+ if (node.moduleReference.type === 'TSExternalModuleReference') {
23
+ pathNode = node.moduleReference.expression;
24
+ }
25
+ }
26
+ else {
27
+ pathNode = node.source;
28
+ }
29
+ return pathNode && pathNode.type === 'Literal' && typeof pathNode.value === 'string' ? pathNode : null;
30
+ }
31
+ exports.getImportPathLiteralNode = getImportPathLiteralNode;
32
+ //# sourceMappingURL=getImportPathLiteralNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getImportPathLiteralNode.js","sourceRoot":"","sources":["../../src/utils/getImportPathLiteralNode.ts"],"names":[],"mappings":";;;AAUA;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,IAAoB;IAC3D,IAAI,QAAQ,GAAyB,IAAI,CAAC;IAE1C,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC;QACtF,MAAM,gBAAgB,GACpB,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB;YACvC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;YACxC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS;YACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;YAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC;QAE1C,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;YAClE,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;SAC9B;KACF;SAAM,IAAI,IAAI,CAAC,IAAI,KAAK,2BAA2B,EAAE;QACpD,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,KAAK,2BAA2B,EAAE;YAC7D,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC;SAC5C;KACF;SAAM;QACL,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;KACxB;IAED,OAAO,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AACzG,CAAC;AAxBD,4DAwBC","sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\n\nexport type ImportLikeNode =\n | TSESTree.CallExpression\n | TSESTree.ExportAllDeclaration\n | TSESTree.ExportNamedDeclaration\n | TSESTree.ImportDeclaration\n | TSESTree.ImportExpression\n | TSESTree.TSImportEqualsDeclaration;\n\n/**\n * Verifies that the given node contains a string literal import path, and if so, returns the path node.\n * (For `CallExpression` nodes, also verifies that the callee is `require` or `require.resolve`.)\n */\nexport function getImportPathLiteralNode(node: ImportLikeNode): TSESTree.StringLiteral | null {\n let pathNode: TSESTree.Node | null = null;\n\n if (node.type === 'CallExpression') {\n const isRequire = node.callee.type === 'Identifier' && node.callee.name === 'require';\n const isRequireResolve =\n node.callee.type === 'MemberExpression' &&\n node.callee.object.type === 'Identifier' &&\n node.callee.object.name === 'require' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'resolve';\n\n if ((isRequire || isRequireResolve) && node.arguments.length === 1) {\n pathNode = node.arguments[0];\n }\n } else if (node.type === 'TSImportEqualsDeclaration') {\n if (node.moduleReference.type === 'TSExternalModuleReference') {\n pathNode = node.moduleReference.expression;\n }\n } else {\n pathNode = node.source;\n }\n\n return pathNode && pathNode.type === 'Literal' && typeof pathNode.value === 'string' ? pathNode : null;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { PackageJson } from './PackageJson';
2
+ export type PackageInfo = {
3
+ /** Path to the package root directory */
4
+ path: string;
5
+ /** Whether this is a package locally developed in the same monorepo */
6
+ isLocal: boolean;
7
+ /** package.json contents */
8
+ json: PackageJson;
9
+ };
10
+ /**
11
+ * Find the package root directory relative to the given file (based on the presence of a
12
+ * package.json file) and read the package.json.
13
+ * @param startPath Search up starting from this file or directory
14
+ */
15
+ export declare function getPackageInfo(startPath: string, debug: boolean): PackageInfo | null;
16
+ //# sourceMappingURL=getPackageInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPackageInfo.d.ts","sourceRoot":"","sources":["../../src/utils/getPackageInfo.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG;IACxB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B;IAC5B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAGF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CA8CpF"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPackageInfo = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const packageInfoCache = new Map();
7
+ /**
8
+ * Find the package root directory relative to the given file (based on the presence of a
9
+ * package.json file) and read the package.json.
10
+ * @param startPath Search up starting from this file or directory
11
+ */
12
+ function getPackageInfo(startPath, debug) {
13
+ // Use the realpath in case this is a linked package in a monorepo
14
+ startPath = fs.realpathSync(startPath);
15
+ const root = path.parse(startPath).root;
16
+ const startDir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);
17
+ let dir = startDir;
18
+ let packageJsonPath = '';
19
+ let found = false;
20
+ while (dir !== root && !found) {
21
+ const cachedInfo = packageInfoCache.get(dir);
22
+ if (cachedInfo !== undefined) {
23
+ packageInfoCache.set(startDir, cachedInfo);
24
+ return cachedInfo;
25
+ }
26
+ packageJsonPath = path.join(dir, 'package.json');
27
+ if (fs.existsSync(packageJsonPath)) {
28
+ found = true;
29
+ }
30
+ else {
31
+ dir = path.dirname(dir);
32
+ }
33
+ }
34
+ if (!found) {
35
+ debug && console.error(`no package.json found searching up from ${startPath}`);
36
+ packageInfoCache.set(startDir, null);
37
+ return null;
38
+ }
39
+ let packageInfo = null;
40
+ try {
41
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
42
+ packageInfo = {
43
+ path: dir,
44
+ // TODO may not work with yarn 3?
45
+ isLocal: !/[\\/]node_modules[\\/]/.test(dir),
46
+ json: packageJson,
47
+ };
48
+ }
49
+ catch (err) {
50
+ debug && console.error(`error reading or parsing ${packageJsonPath}:`, err);
51
+ }
52
+ packageInfoCache.set(dir, packageInfo);
53
+ packageInfoCache.set(startDir, packageInfo);
54
+ return packageInfo;
55
+ }
56
+ exports.getPackageInfo = getPackageInfo;
57
+ //# sourceMappingURL=getPackageInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPackageInfo.js","sourceRoot":"","sources":["../../src/utils/getPackageInfo.ts"],"names":[],"mappings":";;;AAAA,yBAAyB;AACzB,6BAA6B;AAY7B,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA8B,CAAC;AAC/D;;;;GAIG;AACH,SAAgB,cAAc,CAAC,SAAiB,EAAE,KAAc;IAC9D,kEAAkE;IAClE,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC;IAExC,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAE5F,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,IAAI,eAAe,GAAG,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE;QAC7B,MAAM,UAAU,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,UAAU,KAAK,SAAS,EAAE;YAC5B,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC3C,OAAO,UAAU,CAAC;SACnB;QAED,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACjD,IAAI,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE;YAClC,KAAK,GAAG,IAAI,CAAC;SACd;aAAM;YACL,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;SACzB;KACF;IAED,IAAI,CAAC,KAAK,EAAE;QACV,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,2CAA2C,SAAS,EAAE,CAAC,CAAC;QAC/E,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;KACb;IAED,IAAI,WAAW,GAAuB,IAAI,CAAC;IAC3C,IAAI;QACF,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAgB,CAAC;QACxF,WAAW,GAAG;YACZ,IAAI,EAAE,GAAG;YACT,iCAAiC;YACjC,OAAO,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;YAC5C,IAAI,EAAE,WAAW;SAClB,CAAC;KACH;IAAC,OAAO,GAAG,EAAE;QACZ,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,4BAA4B,eAAe,GAAG,EAAE,GAAG,CAAC,CAAC;KAC7E;IACD,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACvC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC5C,OAAO,WAAW,CAAC;AACrB,CAAC;AA9CD,wCA8CC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport type { PackageJson } from './PackageJson';\n\nexport type PackageInfo = {\n /** Path to the package root directory */\n path: string;\n /** Whether this is a package locally developed in the same monorepo */\n isLocal: boolean;\n /** package.json contents */\n json: PackageJson;\n};\n\nconst packageInfoCache = new Map<string, PackageInfo | null>();\n/**\n * Find the package root directory relative to the given file (based on the presence of a\n * package.json file) and read the package.json.\n * @param startPath Search up starting from this file or directory\n */\nexport function getPackageInfo(startPath: string, debug: boolean): PackageInfo | null {\n // Use the realpath in case this is a linked package in a monorepo\n startPath = fs.realpathSync(startPath);\n const root = path.parse(startPath).root;\n\n const startDir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);\n\n let dir = startDir;\n let packageJsonPath = '';\n let found = false;\n while (dir !== root && !found) {\n const cachedInfo = packageInfoCache.get(dir);\n if (cachedInfo !== undefined) {\n packageInfoCache.set(startDir, cachedInfo);\n return cachedInfo;\n }\n\n packageJsonPath = path.join(dir, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n found = true;\n } else {\n dir = path.dirname(dir);\n }\n }\n\n if (!found) {\n debug && console.error(`no package.json found searching up from ${startPath}`);\n packageInfoCache.set(startDir, null);\n return null;\n }\n\n let packageInfo: PackageInfo | null = null;\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as PackageJson;\n packageInfo = {\n path: dir,\n // TODO may not work with yarn 3?\n isLocal: !/[\\\\/]node_modules[\\\\/]/.test(dir),\n json: packageJson,\n };\n } catch (err) {\n debug && console.error(`error reading or parsing ${packageJsonPath}:`, err);\n }\n packageInfoCache.set(dir, packageInfo);\n packageInfoCache.set(startDir, packageInfo);\n return packageInfo;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Verify that the import is relevant for the very specific purposes of the `no-unsupported-imports` rule,
3
+ * and if so, find the package name and sub-path.
4
+ */
5
+ export declare function parseImportIfRelevant(importPath: string, ignorePatterns: RegExp[]): {
6
+ packageName: string;
7
+ subPath: string;
8
+ } | null;
9
+ /**
10
+ * Parse a module ID which is assumed to point to an actual file in a package (no loader prefixes,
11
+ * relative paths, node builtins, or data URIs).
12
+ * @returns Package name and sub-path (if any). Sub-path does *not* include a leading `./`,
13
+ * e.g. `@foo/bar/baz` returns `{ packageName: '@foo/bar', subPath: 'baz' }`.
14
+ */
15
+ export declare function parseModuleId(moduleId: string): {
16
+ packageName: string;
17
+ subPath: string;
18
+ };
19
+ //# sourceMappingURL=parseImportIfRelevant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseImportIfRelevant.d.ts","sourceRoot":"","sources":["../../src/utils/parseImportIfRelevant.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EAAE,GACvB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiBjD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM;;;EAO7C"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseModuleId = exports.parseImportIfRelevant = void 0;
4
+ const module_1 = require("module");
5
+ /**
6
+ * Verify that the import is relevant for the very specific purposes of the `no-unsupported-imports` rule,
7
+ * and if so, find the package name and sub-path.
8
+ */
9
+ function parseImportIfRelevant(importPath, ignorePatterns) {
10
+ // Remove any webpack loader prefix
11
+ // (* is greedy, so it will work even if multiple loaders are specified)
12
+ const normalizedImport = importPath.replace(/.*!/, '');
13
+ // Ignore relative imports, internal imports, builtins, and data URLs
14
+ // (builtinModules includes valid sub-paths like "fs/promises")
15
+ if (!/^([.#]|node:|data:)/.test(normalizedImport) && !module_1.builtinModules.includes(normalizedImport)) {
16
+ // Find the package name and sub-path (if any)
17
+ const { packageName, subPath } = parseModuleId(normalizedImport);
18
+ // Ignore top-level imports, or anything from an ignored package
19
+ if (subPath && !ignorePatterns.some((r) => r.test(normalizedImport))) {
20
+ return { packageName, subPath };
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ exports.parseImportIfRelevant = parseImportIfRelevant;
26
+ /**
27
+ * Parse a module ID which is assumed to point to an actual file in a package (no loader prefixes,
28
+ * relative paths, node builtins, or data URIs).
29
+ * @returns Package name and sub-path (if any). Sub-path does *not* include a leading `./`,
30
+ * e.g. `@foo/bar/baz` returns `{ packageName: '@foo/bar', subPath: 'baz' }`.
31
+ */
32
+ function parseModuleId(moduleId) {
33
+ const nameParts = moduleId.split('/');
34
+ const hasScope = nameParts.length >= 2 && nameParts[0][0] === '@';
35
+ const packageName = hasScope ? `${nameParts[0]}/${nameParts[1]}` : nameParts[0];
36
+ const subPath = nameParts.slice(hasScope ? 2 : 1).join('/');
37
+ return { packageName, subPath };
38
+ }
39
+ exports.parseModuleId = parseModuleId;
40
+ //# sourceMappingURL=parseImportIfRelevant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseImportIfRelevant.js","sourceRoot":"","sources":["../../src/utils/parseImportIfRelevant.ts"],"names":[],"mappings":";;;AAAA,mCAAwC;AAExC;;;GAGG;AACH,SAAgB,qBAAqB,CACnC,UAAkB,EAClB,cAAwB;IAExB,mCAAmC;IACnC,wEAAwE;IACxE,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEvD,qEAAqE;IACrE,+DAA+D;IAC/D,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,uBAAc,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAC/F,8CAA8C;QAC9C,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAEjE,gEAAgE;QAChE,IAAI,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE;YACpE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;SACjC;KACF;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AApBD,sDAoBC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IAElE,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAPD,sCAOC","sourcesContent":["import { builtinModules } from 'module';\n\n/**\n * Verify that the import is relevant for the very specific purposes of the `no-unsupported-imports` rule,\n * and if so, find the package name and sub-path.\n */\nexport function parseImportIfRelevant(\n importPath: string,\n ignorePatterns: RegExp[],\n): { packageName: string; subPath: string } | null {\n // Remove any webpack loader prefix\n // (* is greedy, so it will work even if multiple loaders are specified)\n const normalizedImport = importPath.replace(/.*!/, '');\n\n // Ignore relative imports, internal imports, builtins, and data URLs\n // (builtinModules includes valid sub-paths like \"fs/promises\")\n if (!/^([.#]|node:|data:)/.test(normalizedImport) && !builtinModules.includes(normalizedImport)) {\n // Find the package name and sub-path (if any)\n const { packageName, subPath } = parseModuleId(normalizedImport);\n\n // Ignore top-level imports, or anything from an ignored package\n if (subPath && !ignorePatterns.some((r) => r.test(normalizedImport))) {\n return { packageName, subPath };\n }\n }\n return null;\n}\n\n/**\n * Parse a module ID which is assumed to point to an actual file in a package (no loader prefixes,\n * relative paths, node builtins, or data URIs).\n * @returns Package name and sub-path (if any). Sub-path does *not* include a leading `./`,\n * e.g. `@foo/bar/baz` returns `{ packageName: '@foo/bar', subPath: 'baz' }`.\n */\nexport function parseModuleId(moduleId: string) {\n const nameParts = moduleId.split('/');\n const hasScope = nameParts.length >= 2 && nameParts[0][0] === '@';\n\n const packageName = hasScope ? `${nameParts[0]}/${nameParts[1]}` : nameParts[0];\n const subPath = nameParts.slice(hasScope ? 2 : 1).join('/');\n return { packageName, subPath };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { PackageExports } from './PackageJson.js';
2
+ /**
3
+ * Determine whether `importSubPath` might satisfy any sub-path from the exports map,
4
+ * disregarding conditions and file existence.
5
+ * @param importSubPath Sub-path such as `lib/foo`
6
+ */
7
+ export declare function pathSatisfiesAnyExport(exportsMap: PackageExports | undefined, importSubPath: string): boolean;
8
+ //# sourceMappingURL=pathSatisfiesAnyExport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathSatisfiesAnyExport.d.ts","sourceRoot":"","sources":["../../src/utils/pathSatisfiesAnyExport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,cAAc,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,WA8BnG"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathSatisfiesAnyExport = void 0;
4
+ /**
5
+ * Determine whether `importSubPath` might satisfy any sub-path from the exports map,
6
+ * disregarding conditions and file existence.
7
+ * @param importSubPath Sub-path such as `lib/foo`
8
+ */
9
+ function pathSatisfiesAnyExport(exportsMap, importSubPath) {
10
+ // If the exports map is a string, it's a shorthand for a single export.
11
+ // If it's an array, the contents are fallbacks for a single export.
12
+ if (!exportsMap || typeof exportsMap === 'string' || Array.isArray(exportsMap)) {
13
+ return false;
14
+ }
15
+ importSubPath = `./${importSubPath}`;
16
+ return (
17
+ // Check for very basic exclusions. (Technically a path could also be excluded by setting
18
+ // a condition to null, or by setting a later wildcard to override an earlier one.
19
+ // Handling for this can be added later if needed.)
20
+ exportsMap[importSubPath] !== null &&
21
+ Object.keys(exportsMap).some((key) => {
22
+ // . is the root import, not a sub-path.
23
+ // Starting character other than . means this is a condition, not a path.
24
+ if (key === '.' || key[0] !== '.') {
25
+ return false;
26
+ }
27
+ if (key.includes('*')) {
28
+ return new RegExp(`^${key.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`).test(importSubPath);
29
+ }
30
+ if (key.endsWith('/')) {
31
+ return importSubPath.startsWith(key);
32
+ }
33
+ // Check if the path is a literal match for this key
34
+ return key === importSubPath;
35
+ }));
36
+ }
37
+ exports.pathSatisfiesAnyExport = pathSatisfiesAnyExport;
38
+ //# sourceMappingURL=pathSatisfiesAnyExport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathSatisfiesAnyExport.js","sourceRoot":"","sources":["../../src/utils/pathSatisfiesAnyExport.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,UAAsC,EAAE,aAAqB;IAClG,wEAAwE;IACxE,oEAAoE;IACpE,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QAC9E,OAAO,KAAK,CAAC;KACd;IAED,aAAa,GAAG,KAAK,aAAa,EAAE,CAAC;IAErC,OAAO;IACL,yFAAyF;IACzF,kFAAkF;IAClF,mDAAmD;IAClD,UAAsC,CAAC,aAAa,CAAC,KAAK,IAAI;QAC/D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACnC,wCAAwC;YACxC,yEAAyE;YACzE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;gBACjC,OAAO,KAAK,CAAC;aACd;YACD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;gBACrB,OAAO,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;aAC9F;YACD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;gBACrB,OAAO,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACtC;YACD,oDAAoD;YACpD,OAAO,GAAG,KAAK,aAAa,CAAC;QAC/B,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AA9BD,wDA8BC","sourcesContent":["import type { PackageExports } from './PackageJson.js';\n\n/**\n * Determine whether `importSubPath` might satisfy any sub-path from the exports map,\n * disregarding conditions and file existence.\n * @param importSubPath Sub-path such as `lib/foo`\n */\nexport function pathSatisfiesAnyExport(exportsMap: PackageExports | undefined, importSubPath: string) {\n // If the exports map is a string, it's a shorthand for a single export.\n // If it's an array, the contents are fallbacks for a single export.\n if (!exportsMap || typeof exportsMap === 'string' || Array.isArray(exportsMap)) {\n return false;\n }\n\n importSubPath = `./${importSubPath}`;\n\n return (\n // Check for very basic exclusions. (Technically a path could also be excluded by setting\n // a condition to null, or by setting a later wildcard to override an earlier one.\n // Handling for this can be added later if needed.)\n (exportsMap as Record<string, unknown>)[importSubPath] !== null &&\n Object.keys(exportsMap).some((key) => {\n // . is the root import, not a sub-path.\n // Starting character other than . means this is a condition, not a path.\n if (key === '.' || key[0] !== '.') {\n return false;\n }\n if (key.includes('*')) {\n return new RegExp(`^${key.replace(/\\./g, '\\\\.').replace(/\\*/g, '.*')}$`).test(importSubPath);\n }\n if (key.endsWith('/')) {\n return importSubPath.startsWith(key);\n }\n // Check if the path is a literal match for this key\n return key === importSubPath;\n })\n );\n}\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolve the root directory of `packageName` starting from `fromDir`, with caching.
3
+ * This uses the basic `resolve` package (which doesn't respect exports maps) to resolve
4
+ * `${packageName}/package.json`, which should always exist.
5
+ */
6
+ export declare function resolvePackageRoot(fromDir: string, packageName: string, debug: boolean): string | null;
7
+ //# sourceMappingURL=resolvePackageRoot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvePackageRoot.d.ts","sourceRoot":"","sources":["../../src/utils/resolvePackageRoot.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAyBtG"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePackageRoot = void 0;
4
+ const path = require("path");
5
+ const _resolve = require("resolve");
6
+ // 'resolve' index.js looks like this:
7
+ // var async = require('./lib/async');
8
+ // async.sync = require('./lib/sync');
9
+ // module.exports = async;
10
+ // This does not play nicely with at least one transpilation or interop step, possibly on specific
11
+ // Node versions: `import * as resolve` *usually* gives the `resolve` function, but sometimes it
12
+ // gives an object with property `default`... This is a workaround for either case.
13
+ // eslint-disable-next-line
14
+ const resolveSync = _resolve.sync || _resolve.default?.sync;
15
+ const getCacheKey = (fromDir, packageName) => `${fromDir}::${packageName}`;
16
+ const resolveCache = new Map();
17
+ /**
18
+ * Resolve the root directory of `packageName` starting from `fromDir`, with caching.
19
+ * This uses the basic `resolve` package (which doesn't respect exports maps) to resolve
20
+ * `${packageName}/package.json`, which should always exist.
21
+ */
22
+ function resolvePackageRoot(fromDir, packageName, debug) {
23
+ const cacheKey = getCacheKey(fromDir, packageName);
24
+ const cached = resolveCache.get(cacheKey);
25
+ if (cached !== undefined) {
26
+ return cached;
27
+ }
28
+ const resolvePath = `${packageName}/package.json`;
29
+ let resolved = null;
30
+ try {
31
+ resolved = path.dirname(resolveSync(resolvePath, { basedir: fromDir, preserveSymlinks: false }));
32
+ }
33
+ catch (err) {
34
+ if (debug) {
35
+ // eslint-disable-next-line
36
+ if (err?.code === 'MODULE_NOT_FOUND') {
37
+ console.error(`Failed to resolve ${resolvePath} from ${fromDir} (module not found)`);
38
+ }
39
+ else {
40
+ console.error(`Failed to resolve ${resolvePath} from ${fromDir}: ${err.message || err}`);
41
+ }
42
+ }
43
+ }
44
+ // Cache even if resolution failed so we don't waste time trying again
45
+ resolveCache.set(cacheKey, resolved);
46
+ return resolved;
47
+ }
48
+ exports.resolvePackageRoot = resolvePackageRoot;
49
+ //# sourceMappingURL=resolvePackageRoot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvePackageRoot.js","sourceRoot":"","sources":["../../src/utils/resolvePackageRoot.ts"],"names":[],"mappings":";;;AAAA,6BAA6B;AAC7B,oCAAoC;AAEpC,sCAAsC;AACtC,0CAA0C;AAC1C,0CAA0C;AAC1C,8BAA8B;AAC9B,kGAAkG;AAClG,gGAAgG;AAChG,mFAAmF;AACnF,2BAA2B;AAC3B,MAAM,WAAW,GAAyB,QAAQ,CAAC,IAAI,IAAK,QAAgB,CAAC,OAAO,EAAE,IAAI,CAAC;AAE3F,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,WAAmB,EAAE,EAAE,CAAC,GAAG,OAAO,KAAK,WAAW,EAAE,CAAC;AAC3F,MAAM,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEtD;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,OAAe,EAAE,WAAmB,EAAE,KAAc;IACrF,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,MAAM,KAAK,SAAS,EAAE;QACxB,OAAO,MAAM,CAAC;KACf;IAED,MAAM,WAAW,GAAG,GAAG,WAAW,eAAe,CAAC;IAClD,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,IAAI;QACF,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;KAClG;IAAC,OAAO,GAAG,EAAE;QACZ,IAAI,KAAK,EAAE;YACT,2BAA2B;YAC3B,IAAK,GAAW,EAAE,IAAI,KAAK,kBAAkB,EAAE;gBAC7C,OAAO,CAAC,KAAK,CAAC,qBAAqB,WAAW,SAAS,OAAO,qBAAqB,CAAC,CAAC;aACtF;iBAAM;gBACL,OAAO,CAAC,KAAK,CAAC,qBAAqB,WAAW,SAAS,OAAO,KAAM,GAAa,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;aACrG;SACF;KACF;IAED,sEAAsE;IACtE,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAzBD,gDAyBC","sourcesContent":["import * as path from 'path';\nimport * as _resolve from 'resolve';\n\n// 'resolve' index.js looks like this:\n// var async = require('./lib/async');\n// async.sync = require('./lib/sync');\n// module.exports = async;\n// This does not play nicely with at least one transpilation or interop step, possibly on specific\n// Node versions: `import * as resolve` *usually* gives the `resolve` function, but sometimes it\n// gives an object with property `default`... This is a workaround for either case.\n// eslint-disable-next-line\nconst resolveSync: typeof _resolve.sync = _resolve.sync || (_resolve as any).default?.sync;\n\nconst getCacheKey = (fromDir: string, packageName: string) => `${fromDir}::${packageName}`;\nconst resolveCache = new Map<string, string | null>();\n\n/**\n * Resolve the root directory of `packageName` starting from `fromDir`, with caching.\n * This uses the basic `resolve` package (which doesn't respect exports maps) to resolve\n * `${packageName}/package.json`, which should always exist.\n */\nexport function resolvePackageRoot(fromDir: string, packageName: string, debug: boolean): string | null {\n const cacheKey = getCacheKey(fromDir, packageName);\n const cached = resolveCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const resolvePath = `${packageName}/package.json`;\n let resolved: string | null = null;\n try {\n resolved = path.dirname(resolveSync(resolvePath, { basedir: fromDir, preserveSymlinks: false }));\n } catch (err) {\n if (debug) {\n // eslint-disable-next-line\n if ((err as any)?.code === 'MODULE_NOT_FOUND') {\n console.error(`Failed to resolve ${resolvePath} from ${fromDir} (module not found)`);\n } else {\n console.error(`Failed to resolve ${resolvePath} from ${fromDir}: ${(err as Error).message || err}`);\n }\n }\n }\n\n // Cache even if resolution failed so we don't waste time trying again\n resolveCache.set(cacheKey, resolved);\n return resolved;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@ms-cloudpack/eslint-plugin",
3
+ "version": "0.1.1",
4
+ "description": "A set of ESLint rules for Cloudpack",
5
+ "license": "MIT",
6
+ "main": "lib/index.js",
7
+ "scripts": {
8
+ "build:watch": "cloudpack-scripts build-watch",
9
+ "build": "cloudpack-scripts build",
10
+ "lint:update": "cloudpack-scripts lint-update",
11
+ "lint": "cloudpack-scripts lint",
12
+ "test:watch": "cloudpack-scripts test-watch",
13
+ "test": "cloudpack-scripts test"
14
+ },
15
+ "dependencies": {
16
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
17
+ "@typescript-eslint/parser": "^5.0.0",
18
+ "resolve": "^1.22.0"
19
+ },
20
+ "peerDependencies": {
21
+ "eslint": ">=8.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@ms-cloudpack/eslint-config-base": "*",
25
+ "@ms-cloudpack/scripts": "*",
26
+ "@ms-cloudpack/test-utilities": "*",
27
+ "@types/eslint": "8.21.3",
28
+ "@types/resolve": "1.20.2",
29
+ "@typescript-eslint/utils": "5.55.0"
30
+ },
31
+ "files": [
32
+ "lib/**/!(*.test.*)"
33
+ ]
34
+ }