@aoelite/refpact-ts 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +38 -2
- package/dist/baseline.d.ts +26 -0
- package/dist/baseline.js +65 -0
- package/dist/core/index.d.ts +79 -0
- package/dist/core/index.js +90 -0
- package/dist/core/model.d.ts +340 -0
- package/dist/core/model.js +1 -0
- package/dist/core/results.d.ts +237 -0
- package/dist/core/results.js +1 -0
- package/dist/core/wasm.d.ts +7 -0
- package/dist/core/wasm.js +67 -0
- package/dist/custom.d.ts +129 -0
- package/dist/custom.js +217 -0
- package/dist/filters.d.ts +73 -0
- package/dist/filters.js +39 -0
- package/dist/frontend/checks.d.ts +15 -0
- package/dist/frontend/checks.js +12 -0
- package/dist/frontend/classic.d.ts +3 -0
- package/dist/frontend/classic.js +105 -0
- package/dist/frontend/compiler.d.ts +93 -0
- package/dist/frontend/compiler.js +116 -0
- package/dist/frontend/components.d.ts +29 -0
- package/dist/frontend/components.js +111 -0
- package/dist/frontend/declarations.d.ts +45 -0
- package/dist/frontend/declarations.js +158 -0
- package/dist/frontend/dependencies.d.ts +53 -0
- package/dist/frontend/dependencies.js +337 -0
- package/dist/frontend/imports.d.ts +149 -0
- package/dist/frontend/imports.js +765 -0
- package/dist/frontend/native.d.ts +3 -0
- package/dist/frontend/native.js +165 -0
- package/dist/frontend/paths.d.ts +10 -0
- package/dist/frontend/paths.js +18 -0
- package/dist/frontend/project.d.ts +118 -0
- package/dist/frontend/project.js +775 -0
- package/dist/group.d.ts +74 -0
- package/dist/group.js +106 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +27 -0
- package/dist/items.d.ts +133 -0
- package/dist/items.js +179 -0
- package/dist/layers.d.ts +112 -0
- package/dist/layers.js +215 -0
- package/dist/onion.d.ts +78 -0
- package/dist/onion.js +96 -0
- package/dist/patterns.d.ts +14 -0
- package/dist/patterns.js +15 -0
- package/dist/rules.d.ts +408 -0
- package/dist/rules.js +655 -0
- package/package.json +52 -4
- package/wasm/refpact_core.wasm +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
/** Loads the client of the installed TypeScript 7. */
|
|
10
|
+
export async function loadNative(installed) {
|
|
11
|
+
const is71 = "./sync" in installed.exports;
|
|
12
|
+
const entry = is71 ? installed.packageName : `${installed.packageName}/unstable`;
|
|
13
|
+
// The numbers of the kinds differ between 7.0 and 7.1, so the value must be the loaded one.
|
|
14
|
+
const ast = (await import(__rewriteRelativeImportExtension(`${entry}/ast`)));
|
|
15
|
+
const sync = (await import(__rewriteRelativeImportExtension(`${entry}/sync`)));
|
|
16
|
+
const useOS = is71 ? (await import(__rewriteRelativeImportExtension(`${entry}/fs`))).serverFS.useOS : undefined;
|
|
17
|
+
const client = { is71, API: sync.API, SymbolFlags: sync.SymbolFlags, useOS };
|
|
18
|
+
return {
|
|
19
|
+
SyntaxKind: ast.SyntaxKind,
|
|
20
|
+
jsDocBesideChildren: false,
|
|
21
|
+
openProjects: (root, configFiles, files) => openProjects(client, root, configFiles, files),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Opens the given tsconfig files in one compiler process. */
|
|
25
|
+
function openProjects(client, root, configFiles, files) {
|
|
26
|
+
// With a file system, the compiler asks this process for each file that it reads.
|
|
27
|
+
const api = new client.API(files !== undefined ? { cwd: root, fs: fileSystem(client, files) } : { cwd: root });
|
|
28
|
+
let snapshot;
|
|
29
|
+
try {
|
|
30
|
+
let project;
|
|
31
|
+
if (client.is71) {
|
|
32
|
+
const opened = api.createSnapshot({ openProjects: [...configFiles] });
|
|
33
|
+
snapshot = opened;
|
|
34
|
+
project = (configFile) => {
|
|
35
|
+
const found = opened.getConfiguredProject(configFile);
|
|
36
|
+
return found && { program: found.program, checker: found.checker, options: found.parsedCommandLine.options };
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const opened = api.updateSnapshot({ openProjects: [...configFiles] });
|
|
41
|
+
snapshot = opened;
|
|
42
|
+
project = (configFile) => {
|
|
43
|
+
const found = opened.getProject(configFile);
|
|
44
|
+
return found && { program: found.program, checker: found.checker, options: found.compilerOptions };
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const projects = configFiles.map((configFile) => {
|
|
48
|
+
const found = project(configFile);
|
|
49
|
+
if (found === undefined) {
|
|
50
|
+
throw new Error(`refpact: the TypeScript compiler did not load ${configFile}`);
|
|
51
|
+
}
|
|
52
|
+
return wrap(client, configFile, found, () => api.clearSourceFileCache());
|
|
53
|
+
});
|
|
54
|
+
const opened = snapshot;
|
|
55
|
+
return {
|
|
56
|
+
projects,
|
|
57
|
+
close() {
|
|
58
|
+
opened.dispose();
|
|
59
|
+
api.close();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
snapshot?.dispose();
|
|
65
|
+
api.close();
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The callbacks of the file system in the form of the client. In 7.0 a callback that is
|
|
71
|
+
* left out, or that gives `undefined`, lets the compiler read the disk. In 7.1 each callback
|
|
72
|
+
* must be there, and a value of the client says the same.
|
|
73
|
+
*/
|
|
74
|
+
function fileSystem(client, files) {
|
|
75
|
+
if (!client.is71) {
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
const useOS = client.useOS;
|
|
79
|
+
return {
|
|
80
|
+
readFile: (fileName) => files.readFile(fileName) ?? useOS,
|
|
81
|
+
fileExists: (fileName) => files.fileExists(fileName) ?? useOS,
|
|
82
|
+
getAccessibleEntries(directoryName) {
|
|
83
|
+
const entries = files.getAccessibleEntries(directoryName);
|
|
84
|
+
return entries === undefined ? useOS : { ...entries, symlinks: undefined };
|
|
85
|
+
},
|
|
86
|
+
// A component file is shown as a plain file.
|
|
87
|
+
stat: (fileName) => {
|
|
88
|
+
const text = files.readFile(fileName);
|
|
89
|
+
return text === undefined ? useOS : { mode: 0o100644, size: text.length, mtime: new Date(0) };
|
|
90
|
+
},
|
|
91
|
+
directoryExists: useOS,
|
|
92
|
+
realpath: useOS,
|
|
93
|
+
writeFile: useOS,
|
|
94
|
+
removeFile: useOS,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function wrap(client, configFile, { program, checker, options }, forget) {
|
|
98
|
+
// TypeScript 7 removed `baseUrl` (error TS5102) and does not resolve with it. A project
|
|
99
|
+
// that sets it would be read with those imports seen as packages, and rules on the files
|
|
100
|
+
// would pass without a check.
|
|
101
|
+
if (options.baseUrl !== undefined) {
|
|
102
|
+
throw new Error(`refpact: ${configFile} sets baseUrl, which TypeScript 7 removed. Use \`paths\` instead.`);
|
|
103
|
+
}
|
|
104
|
+
const ownFiles = program.getSourceFileNames().filter((file) => {
|
|
105
|
+
const metadata = program.getSourceFileMetadata(file);
|
|
106
|
+
return metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary;
|
|
107
|
+
});
|
|
108
|
+
// Each answer of the compiler is a round trip to another process, so each is kept. Many
|
|
109
|
+
// files import the same module, and many names lead through the same re-export.
|
|
110
|
+
const aliases = new Map();
|
|
111
|
+
const exports = new Map();
|
|
112
|
+
const owners = new Map();
|
|
113
|
+
const Flags = client.SymbolFlags;
|
|
114
|
+
const memberFlags = Flags.Property | Flags.Method | Flags.GetAccessor | Flags.SetAccessor | Flags.EnumMember;
|
|
115
|
+
const ownerFlags = Flags.Class | Flags.Interface | Flags.RegularEnum | Flags.ConstEnum;
|
|
116
|
+
return {
|
|
117
|
+
configFile,
|
|
118
|
+
options: options,
|
|
119
|
+
ownFiles,
|
|
120
|
+
sourceFile: (file) => program.getSourceFile(file),
|
|
121
|
+
forgetSourceFiles: forget,
|
|
122
|
+
symbolsAt: (nodes) => (nodes.length === 0 ? [] : checker.getSymbolAtLocation(nodes)),
|
|
123
|
+
shorthandValueAt: (property) => checker.getShorthandAssignmentValueSymbol(property),
|
|
124
|
+
filesOf: (symbol) => symbol.declarations.map((declaration) => declaration.path),
|
|
125
|
+
same: (a, b) => a.id === b.id,
|
|
126
|
+
ownerOf(given) {
|
|
127
|
+
const symbol = given;
|
|
128
|
+
if ((symbol.flags & memberFlags) === 0) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
if (owners.has(symbol.id)) {
|
|
132
|
+
return owners.get(symbol.id);
|
|
133
|
+
}
|
|
134
|
+
const parent = symbol.getParent();
|
|
135
|
+
const found = parent !== undefined && (parent.flags & ownerFlags) !== 0 ? parent : undefined;
|
|
136
|
+
owners.set(symbol.id, found);
|
|
137
|
+
return found;
|
|
138
|
+
},
|
|
139
|
+
isTypeParameter: (symbol) => (symbol.flags & Flags.TypeParameter) !== 0,
|
|
140
|
+
immediateAlias(given) {
|
|
141
|
+
const symbol = given;
|
|
142
|
+
// The compiler stops with a panic when it is asked for the target of a symbol that
|
|
143
|
+
// is not an alias, so the flag is checked first.
|
|
144
|
+
if ((symbol.flags & client.SymbolFlags.Alias) === 0) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
if (aliases.has(symbol.id)) {
|
|
148
|
+
return aliases.get(symbol.id);
|
|
149
|
+
}
|
|
150
|
+
const target = checker.getImmediateAliasedSymbol(symbol);
|
|
151
|
+
const found = target === undefined || checker.isUnknownSymbol(target) ? undefined : target;
|
|
152
|
+
aliases.set(symbol.id, found);
|
|
153
|
+
return found;
|
|
154
|
+
},
|
|
155
|
+
exportsOf(given) {
|
|
156
|
+
const module = given;
|
|
157
|
+
let found = exports.get(module.id);
|
|
158
|
+
if (found === undefined) {
|
|
159
|
+
found = new Map(checker.getExportsOfModule(module).map((symbol) => [symbol.name, symbol]));
|
|
160
|
+
exports.set(module.id, found);
|
|
161
|
+
}
|
|
162
|
+
return found;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function exportSegment(name: string): string;
|
|
2
|
+
export declare function packageSegment(name: string): string;
|
|
3
|
+
export declare function globalSegment(name: string): string;
|
|
4
|
+
export declare function nodeSegment(name: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* How reports print these paths, the way an import names them: `src/db/client.ts#query`,
|
|
7
|
+
* `lodash/fp#map`. An export joins without a separator, and a package loses `npm:`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ATTACHED_PREFIXES: readonly string[];
|
|
10
|
+
export declare const HIDDEN_PREFIXES: readonly string[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function exportSegment(name) {
|
|
2
|
+
return `#${name}`;
|
|
3
|
+
}
|
|
4
|
+
export function packageSegment(name) {
|
|
5
|
+
return `npm:${name}`;
|
|
6
|
+
}
|
|
7
|
+
export function globalSegment(name) {
|
|
8
|
+
return `global:${name}`;
|
|
9
|
+
}
|
|
10
|
+
export function nodeSegment(name) {
|
|
11
|
+
return `node:${name}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* How reports print these paths, the way an import names them: `src/db/client.ts#query`,
|
|
15
|
+
* `lodash/fp#map`. An export joins without a separator, and a package loses `npm:`.
|
|
16
|
+
*/
|
|
17
|
+
export const ATTACHED_PREFIXES = ["#"];
|
|
18
|
+
export const HIDDEN_PREFIXES = ["npm:"];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { FactSet } from "../core/index.ts";
|
|
2
|
+
import type { ComponentExtension } from "./components.ts";
|
|
3
|
+
/** Options of {@link loadProject}. */
|
|
4
|
+
export interface LoadOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The directory that file paths in rules are relative to. Files outside it are not units.
|
|
7
|
+
* Default: the directory of the first tsconfig file.
|
|
8
|
+
*/
|
|
9
|
+
readonly root?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Resolves what `object.name` names when the object is a value: a member of a class, of an
|
|
12
|
+
* interface or of an enum of the project. Each such use in another file is a reference of
|
|
13
|
+
* the kind `member` to `file.ts#Owner#name`, so that `order.total()` is a use of
|
|
14
|
+
* `src/domain/order.ts#Order#total`.
|
|
15
|
+
*
|
|
16
|
+
* The load then reads every function body and asks the compiler about each access, which
|
|
17
|
+
* takes longer. Default: `false`.
|
|
18
|
+
*/
|
|
19
|
+
readonly resolveMembers?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Lets a use of a member of an interface or of a class also count as a use of the same
|
|
22
|
+
* member of each class of the project that implements or extends it. `store.save(order)`,
|
|
23
|
+
* where `store` is a `Store`, is then also a **candidate** use of `PgStore#save`, because
|
|
24
|
+
* the call can run it. A rule about what the code can reach, such as `mustNotReach`, then
|
|
25
|
+
* follows the call into the classes.
|
|
26
|
+
*
|
|
27
|
+
* This turns on {@link LoadOptions.resolveMembers}. It is not the default, because it
|
|
28
|
+
* turns around what an interface is for: with it, the code that calls the interface uses
|
|
29
|
+
* the files that implement it. A rule leaves the candidates out with
|
|
30
|
+
* `ignoringCandidates()`. Default: `false`.
|
|
31
|
+
*/
|
|
32
|
+
readonly followImplementations?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Reads the code of component files: `.svelte`, `.vue` and `.astro`. `true` reads all
|
|
35
|
+
* three, and a list names the extensions to read.
|
|
36
|
+
*
|
|
37
|
+
* Each component file is a file of the project under its own name, `src/App.svelte`. Its
|
|
38
|
+
* code is what its `<script>` blocks hold, and for Astro also the part between the `---`
|
|
39
|
+
* lines at its start. The imports and the globals of that code are references as in a
|
|
40
|
+
* TypeScript file, at their lines and columns in the component, and an import of
|
|
41
|
+
* `./App.svelte` leads to the component.
|
|
42
|
+
*
|
|
43
|
+
* The markup is not read: a global or a member that only the markup uses is not seen. A
|
|
44
|
+
* component that the markup uses is imported in the script, so no import is missed.
|
|
45
|
+
*
|
|
46
|
+
* The compiler then asks refpact for each file that it reads, which makes the load
|
|
47
|
+
* slower. Default: `false`.
|
|
48
|
+
*/
|
|
49
|
+
readonly components?: boolean | readonly ComponentExtension[];
|
|
50
|
+
/**
|
|
51
|
+
* Reads the dependencies that the `package.json` files name, and the packages that those
|
|
52
|
+
* need.
|
|
53
|
+
*
|
|
54
|
+
* The code shows which packages a file imports. A `package.json` shows which packages are
|
|
55
|
+
* installed, also those that no code imports. With this option, each `package.json` that a
|
|
56
|
+
* file of the project belongs to is a file of the project under its path, `package.json`
|
|
57
|
+
* or `packages/app/package.json`, and each dependency is a reference from it to the
|
|
58
|
+
* package, at the line of its key:
|
|
59
|
+
*
|
|
60
|
+
* - `dependency`, `dev-dependency`, `peer-dependency` and `optional-dependency` for what
|
|
61
|
+
* the file names. A package that another `package.json` of the project declares is also
|
|
62
|
+
* a reference to that file, so the rules about files, such as `mustBeAcyclic`, see the
|
|
63
|
+
* dependencies between the packages of a monorepo.
|
|
64
|
+
* - `transitive-dependency` for each other package that a dependency needs, with the
|
|
65
|
+
* packages on the way in the note of the violation, and `transitive-dev-dependency`
|
|
66
|
+
* when only the `devDependencies` need it. They are found in the `node_modules`
|
|
67
|
+
* directories, as Node finds them; a package that is not installed is not followed.
|
|
68
|
+
*
|
|
69
|
+
* A `package.json` with a name declares one item of the kind `package`.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* const app = loadProject("tsconfig.json", { readDependencies: true });
|
|
73
|
+
* rule("the app is installed without lodash")
|
|
74
|
+
* .files(anyOf("package.json"))
|
|
75
|
+
* .mustNotImport(anyOf(pkg("lodash")))
|
|
76
|
+
* .onlyKinds("dependency", "transitive-dependency")
|
|
77
|
+
* .check(app);
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* `mustOnlyImportDeclaredPackages` and `mustNotImportDevDependencies` need this option.
|
|
81
|
+
*
|
|
82
|
+
* Each of these references has the version and the licence of the installed package as
|
|
83
|
+
* `properties`, for a check of your own: `seen.reference.properties?.["license"]`. Name
|
|
84
|
+
* them in the rule, with `readingProperties`.
|
|
85
|
+
*
|
|
86
|
+
* It is not the default: a `package.json` is then a file that `all()` selects, so a rule
|
|
87
|
+
* with a list of what the files may import would have to name every package, and a rule
|
|
88
|
+
* such as `mustBeImported` would have to leave the file out. The load reads the
|
|
89
|
+
* `package.json` of each installed package that the dependencies lead to. Default: `false`.
|
|
90
|
+
*/
|
|
91
|
+
readonly readDependencies?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** One or more analysed TypeScript projects. */
|
|
94
|
+
export declare class Project {
|
|
95
|
+
/** The directory that file paths are relative to. */
|
|
96
|
+
readonly root: string;
|
|
97
|
+
/** The tsconfig files that were loaded, also those found through `references`. */
|
|
98
|
+
readonly configFiles: readonly string[];
|
|
99
|
+
/** The facts, for the core. */
|
|
100
|
+
readonly facts: FactSet;
|
|
101
|
+
/** @internal */
|
|
102
|
+
constructor(root: string, configFiles: readonly string[], facts: FactSet);
|
|
103
|
+
/** The paths of all files, relative to the root, such as `src/app/main.ts`. */
|
|
104
|
+
get files(): string[];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Loads the project of a tsconfig file, and the projects it names in `references`. A
|
|
108
|
+
* directory stands for its `tsconfig.json`, or for its `jsconfig.json` when it has none. A
|
|
109
|
+
* directory with neither is read with default options: all its files below it except
|
|
110
|
+
* `node_modules`, JavaScript too, with the resolution of a bundler, which finds `./db` and
|
|
111
|
+
* `./db.js` alike. A JavaScript project is also read through a tsconfig file with `allowJs`.
|
|
112
|
+
*
|
|
113
|
+
* Imports are resolved by the installed TypeScript compiler, with the options of the project
|
|
114
|
+
* that owns the file: `paths`, package `exports` and `imports`, and project references.
|
|
115
|
+
*/
|
|
116
|
+
export declare function loadProject(tsconfig: string | readonly string[], options?: LoadOptions): Project;
|
|
117
|
+
/** Parses JSON that may have comments and trailing commas, as tsconfig files do. */
|
|
118
|
+
export declare function parseJsonWithComments(text: string, file: string): unknown;
|