@aoelite/refpact-ts 0.0.0-stage → 0.2.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/baseline.d.ts +26 -0
  4. package/dist/baseline.js +65 -0
  5. package/dist/core/index.d.ts +88 -0
  6. package/dist/core/index.js +105 -0
  7. package/dist/core/model.d.ts +340 -0
  8. package/dist/core/model.js +1 -0
  9. package/dist/core/results.d.ts +268 -0
  10. package/dist/core/results.js +1 -0
  11. package/dist/core/wasm.d.ts +7 -0
  12. package/dist/core/wasm.js +67 -0
  13. package/dist/custom.d.ts +166 -0
  14. package/dist/custom.js +239 -0
  15. package/dist/filters.d.ts +73 -0
  16. package/dist/filters.js +39 -0
  17. package/dist/frontend/checks.d.ts +15 -0
  18. package/dist/frontend/checks.js +12 -0
  19. package/dist/frontend/classic.d.ts +3 -0
  20. package/dist/frontend/classic.js +105 -0
  21. package/dist/frontend/compiler.d.ts +93 -0
  22. package/dist/frontend/compiler.js +116 -0
  23. package/dist/frontend/components.d.ts +29 -0
  24. package/dist/frontend/components.js +111 -0
  25. package/dist/frontend/declarations.d.ts +45 -0
  26. package/dist/frontend/declarations.js +158 -0
  27. package/dist/frontend/dependencies.d.ts +53 -0
  28. package/dist/frontend/dependencies.js +337 -0
  29. package/dist/frontend/imports.d.ts +149 -0
  30. package/dist/frontend/imports.js +765 -0
  31. package/dist/frontend/native.d.ts +3 -0
  32. package/dist/frontend/native.js +165 -0
  33. package/dist/frontend/paths.d.ts +10 -0
  34. package/dist/frontend/paths.js +18 -0
  35. package/dist/frontend/project.d.ts +118 -0
  36. package/dist/frontend/project.js +775 -0
  37. package/dist/group.d.ts +74 -0
  38. package/dist/group.js +106 -0
  39. package/dist/index.d.ts +35 -0
  40. package/dist/index.js +27 -0
  41. package/dist/items.d.ts +133 -0
  42. package/dist/items.js +179 -0
  43. package/dist/layers.d.ts +112 -0
  44. package/dist/layers.js +215 -0
  45. package/dist/onion.d.ts +78 -0
  46. package/dist/onion.js +96 -0
  47. package/dist/patterns.d.ts +14 -0
  48. package/dist/patterns.js +15 -0
  49. package/dist/rules.d.ts +408 -0
  50. package/dist/rules.js +665 -0
  51. package/package.json +52 -4
  52. package/wasm/refpact_core.wasm +0 -0
@@ -0,0 +1,3 @@
1
+ import type { Backend, Installed } from "./compiler.ts";
2
+ /** Loads the client of the installed TypeScript 7. */
3
+ export declare function loadNative(installed: Installed): Promise<Backend>;
@@ -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;