@crutchcrew/oxlint-plugin-import-access 1.0.0 β 1.1.0
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/README.md +5 -5
- package/dist/core/checkImportability.d.ts +7 -1
- package/dist/index.js +43 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# oxlint-plugin-import-access π¦π
|
|
1
|
+
# @crutchcrew/oxlint-plugin-import-access π¦π
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@crutchcrew/oxlint-plugin-import-access)
|
|
4
4
|
[](https://github.com/crutchcrew/oxlint-plugin-import-access/actions)
|
|
5
5
|
[](https://www.npmjs.com/package/@crutchcrew/oxlint-plugin-import-access)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
JSDoc import access for TypeScript, enforced by [oxlint](https://oxc.rs/docs/guide/usage/linter). Only `@public` exports can be imported from anywhere, and an export without an access tag is `@package`: only files in its own directory can import it. Tag an export `@public` to share it with the whole project, or `@private` so no other file can import it. To make untagged exports `@public` or `@private` instead, set [`defaultImportability`](#options).
|
|
8
|
+
JSDoc import access for TypeScript, enforced by [oxlint](https://oxc.rs/docs/guide/usage/linter). Only `@public` exports can be imported from anywhere, and an export without an access tag is `@package`: only files in its own package (its directory and subdirectories) can import it. Tag an export `@public` to share it with the whole project, or `@private` so no other file can import it. To make untagged exports `@public` or `@private` instead, set [`defaultImportability`](#options).
|
|
9
9
|
|
|
10
10
|
A native oxlint JS plugin port of [eslint-plugin-import-access](https://github.com/uhyo/eslint-plugin-import-access) that needs **no type checker**: no `typescript` dependency, no `tsconfig.json` project loading, no typed linting setup.
|
|
11
11
|
|
|
@@ -38,7 +38,7 @@ This plugin adds a directory-level layer between a file and the whole project: a
|
|
|
38
38
|
| **Checks unsaved edits in the editor** | β
| β
| β skipped until saved |
|
|
39
39
|
| **Same results as the original rule** | β
verifiedΒΉ | β | β
|
|
|
40
40
|
|
|
41
|
-
ΒΉ With `defaultImportability: "public"`, which makes exports without an access tag `@public` like in the original. This plugin's default is `"package"`.
|
|
41
|
+
ΒΉ With `defaultImportability: "public"`, which makes exports without an access tag `@public` like in the original. This plugin's default is `"package"`. This plugin also checks the members used through namespace imports (`import * as foo`), which the original doesn't check.
|
|
42
42
|
|
|
43
43
|
## Install
|
|
44
44
|
|
|
@@ -110,11 +110,11 @@ The original rule asks the TypeScript type checker which declaration an import r
|
|
|
110
110
|
|
|
111
111
|
1. resolve the import like TypeScript (`tsconfig` `paths`, `package.json` `exports`, `.js` β `.ts`) with [oxc-resolver](https://github.com/oxc-project/oxc-resolver),
|
|
112
112
|
2. skip builtins and anything reached through `node_modules`,
|
|
113
|
-
3. read the target file's exports and JSDoc with [oxc-parser](https://github.com/oxc-project/oxc/tree/main/napi/parser) β module record and comments only,
|
|
113
|
+
3. read the target file's exports and JSDoc with [oxc-parser](https://github.com/oxc-project/oxc/tree/main/napi/parser) β the module record and comments; the syntax tree only for the few files with decorators, `export =`, re-exported imports or JSDoc inside variable statements β and cache them,
|
|
114
114
|
4. follow `export *`, stop at direct re-exports like upstream, and read JSDoc with TypeScript's attachment and tag rules,
|
|
115
115
|
5. check ambient `declare module "β¦"` and wildcard `declare module "*.svg"` declarations too.
|
|
116
116
|
|
|
117
|
-
**Verified against the original:** the upstream test suite passes unchanged (same lines, columns, messages), and
|
|
117
|
+
**Verified against the original:** the upstream test suite passes unchanged (same lines, columns, messages), and the port was compared with upstream's TypeScript-based rule on every fixture file and option combination, including new fixtures for re-export chains, overloads, enums, `export =`, wildcard modules and JSDoc placement, whose expectations in the test suite were generated by the original rule. The [known differences](./docs/rule-jsdoc.md#differences-from-upstream) are edge cases such as module augmentation across files.
|
|
118
118
|
|
|
119
119
|
## Performance
|
|
120
120
|
|
|
@@ -39,7 +39,8 @@ export declare class ImportabilityChecker {
|
|
|
39
39
|
constructor(watching?: boolean);
|
|
40
40
|
/**
|
|
41
41
|
* Start checking `fileName`. In a long-running host, everything read for earlier files is
|
|
42
|
-
* re-validated: exporter files are stat-ed again
|
|
42
|
+
* re-validated: exporter files are stat-ed again, specifiers resolved again and package names
|
|
43
|
+
* looked up again.
|
|
43
44
|
*/
|
|
44
45
|
nextFile(fileName: string): void;
|
|
45
46
|
clear(): void;
|
|
@@ -52,6 +53,11 @@ export declare class ImportabilityPolicy {
|
|
|
52
53
|
#private;
|
|
53
54
|
readonly options: PackageOptions;
|
|
54
55
|
constructor(options: PackageOptions);
|
|
56
|
+
/**
|
|
57
|
+
* Whether some export of `specifier` could be reported. Cheap way to skip namespace imports
|
|
58
|
+
* of external modules and of modules whose exports are all public.
|
|
59
|
+
*/
|
|
60
|
+
mayReport(checker: ImportabilityChecker, importerFileName: string, specifier: string): boolean;
|
|
55
61
|
/**
|
|
56
62
|
* Check importing `name` (the export name, `default` for default imports) via `specifier`.
|
|
57
63
|
*/
|
package/dist/index.js
CHANGED
|
@@ -737,6 +737,9 @@ function lookupPackageName(file) {
|
|
|
737
737
|
}
|
|
738
738
|
return result;
|
|
739
739
|
}
|
|
740
|
+
function clearPackageNameCache() {
|
|
741
|
+
packageNameCache.clear();
|
|
742
|
+
}
|
|
740
743
|
|
|
741
744
|
// src/core/resolveModule.ts
|
|
742
745
|
import { realpathSync } from "node:fs";
|
|
@@ -903,6 +906,7 @@ class ImportabilityChecker {
|
|
|
903
906
|
}
|
|
904
907
|
this.exportTables.nextGeneration();
|
|
905
908
|
this.resolver.clear();
|
|
909
|
+
clearPackageNameCache();
|
|
906
910
|
this.#found = new WeakMap;
|
|
907
911
|
}
|
|
908
912
|
clear() {
|
|
@@ -910,6 +914,7 @@ class ImportabilityChecker {
|
|
|
910
914
|
this.resolver.clear();
|
|
911
915
|
this.exportTables.clear();
|
|
912
916
|
this.ambientModules.clear();
|
|
917
|
+
clearPackageNameCache();
|
|
913
918
|
}
|
|
914
919
|
}
|
|
915
920
|
|
|
@@ -936,10 +941,17 @@ class ImportabilityPolicy {
|
|
|
936
941
|
const packageName = lookupPackageName(importerFileName);
|
|
937
942
|
return packageName !== null && (specifier === packageName || specifier.startsWith(`${packageName}/`));
|
|
938
943
|
}
|
|
939
|
-
|
|
944
|
+
#targetModule(checker, importerFileName, specifier) {
|
|
940
945
|
const { defaultImportability, projectDirectory } = this.options;
|
|
941
946
|
const assumeAllPublic = defaultImportability === "public";
|
|
942
|
-
|
|
947
|
+
return checker.targetModule(importerFileName, specifier, projectDirectory, assumeAllPublic);
|
|
948
|
+
}
|
|
949
|
+
mayReport(checker, importerFileName, specifier) {
|
|
950
|
+
return this.#targetModule(checker, importerFileName, specifier)?.names !== undefined;
|
|
951
|
+
}
|
|
952
|
+
check(checker, importerFileName, specifier, name) {
|
|
953
|
+
const { defaultImportability } = this.options;
|
|
954
|
+
const module = this.#targetModule(checker, importerFileName, specifier);
|
|
943
955
|
const found = module && checker.findExport(module, name);
|
|
944
956
|
if (found === undefined || !found.entries[0].checked) {
|
|
945
957
|
return;
|
|
@@ -998,6 +1010,21 @@ function realFileName(fileName) {
|
|
|
998
1010
|
function nameOf(node) {
|
|
999
1011
|
return node.type === "Literal" ? node.value : node.name;
|
|
1000
1012
|
}
|
|
1013
|
+
function accessedMember(identifier) {
|
|
1014
|
+
const parent = identifier.parent;
|
|
1015
|
+
if (parent.type === "MemberExpression" && parent.object === identifier) {
|
|
1016
|
+
const { property } = parent;
|
|
1017
|
+
if (property.type === "Identifier" && !parent.computed) {
|
|
1018
|
+
return { node: property, name: property.name };
|
|
1019
|
+
}
|
|
1020
|
+
if (property.type === "Literal" && typeof property.value === "string") {
|
|
1021
|
+
return { node: property, name: property.value };
|
|
1022
|
+
}
|
|
1023
|
+
} else if (parent.type === "TSQualifiedName") {
|
|
1024
|
+
return { node: parent.right, name: parent.right.name };
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1001
1028
|
var rule = {
|
|
1002
1029
|
meta: {
|
|
1003
1030
|
type: "problem",
|
|
@@ -1055,6 +1082,20 @@ var rule = {
|
|
|
1055
1082
|
ImportDefaultSpecifier(node) {
|
|
1056
1083
|
check(node, node.parent.source.value, "default", false);
|
|
1057
1084
|
},
|
|
1085
|
+
ImportNamespaceSpecifier(node) {
|
|
1086
|
+
const specifier = node.parent.source.value;
|
|
1087
|
+
if (!policy.mayReport(checker, importer, specifier)) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
for (const variable of context.sourceCode.getDeclaredVariables(node)) {
|
|
1091
|
+
for (const { identifier } of variable.references) {
|
|
1092
|
+
const member = accessedMember(identifier);
|
|
1093
|
+
if (member !== undefined) {
|
|
1094
|
+
check(member.node, specifier, member.name, false);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1058
1099
|
ExportSpecifier(node) {
|
|
1059
1100
|
const { source } = node.parent;
|
|
1060
1101
|
if (source !== null) {
|
package/package.json
CHANGED