@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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filters of a rule: what it leaves out, why it exists and how much it weighs.
|
|
3
|
+
*
|
|
4
|
+
* A rule, layers and an onion architecture have the same filters. Each filter is written
|
|
5
|
+
* once, here: {@link addFilters} gives its method to all of them, so a new filter cannot be
|
|
6
|
+
* missing from one.
|
|
7
|
+
*/
|
|
8
|
+
import type { Rule as CoreRule } from "./core/index.ts";
|
|
9
|
+
import type { Kind } from "./frontend/imports.ts";
|
|
10
|
+
import type { AnyOf } from "./rules.ts";
|
|
11
|
+
/** One filter, as a value that a set of rules keeps until its rules are made. @internal */
|
|
12
|
+
export interface Filter {
|
|
13
|
+
/**
|
|
14
|
+
* Whether the filter says which imports a rule sees. A rule that looks at no import, such
|
|
15
|
+
* as "every file is in a layer", does not get such a filter: there, an exemption that lets
|
|
16
|
+
* nothing pass would be an error.
|
|
17
|
+
*/
|
|
18
|
+
readonly aboutImports: boolean;
|
|
19
|
+
/** The rule of the core with the filter. */
|
|
20
|
+
readonly apply: (rule: CoreRule) => CoreRule;
|
|
21
|
+
}
|
|
22
|
+
/** What has filters: it gives itself again with one more filter. @internal */
|
|
23
|
+
export interface Filtered<Self> {
|
|
24
|
+
/** @internal */
|
|
25
|
+
filtered(filter: Filter): Self;
|
|
26
|
+
}
|
|
27
|
+
/** The filters of a rule, of layers and of an onion architecture. */
|
|
28
|
+
export interface Filters<Self> {
|
|
29
|
+
/**
|
|
30
|
+
* Says why this exists. The report of a violation prints the reason below the name of the
|
|
31
|
+
* rule, so whoever breaks the rule learns what it protects.
|
|
32
|
+
*/
|
|
33
|
+
because(reason: string): Self;
|
|
34
|
+
/**
|
|
35
|
+
* Makes this a warning: the violations are reported with `console.warn`, and the check
|
|
36
|
+
* passes. A rule that cannot fail as written is still an error, and so is a stale
|
|
37
|
+
* baseline. {@link ImportRule.evaluate} gives the violations of a warning as of any rule.
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* rule("new code does not print").files(all()).mustNotContain("console").asWarning().check(app);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
asWarning(): Self;
|
|
44
|
+
/** Counts only these kinds of import. */
|
|
45
|
+
onlyKinds(...kinds: Kind[]): Self;
|
|
46
|
+
/** Counts all kinds of import except these. */
|
|
47
|
+
ignoringKinds(...kinds: Kind[]): Self;
|
|
48
|
+
/**
|
|
49
|
+
* Leaves out the imports that the analysis could not prove: the files that a computed
|
|
50
|
+
* specifier such as `` import(`./locales/${name}.ts`) `` can name.
|
|
51
|
+
*/
|
|
52
|
+
ignoringCandidates(): Self;
|
|
53
|
+
/** Leaves out `import type`, type-only names, `import("m")` in types, and `export type .. from`. */
|
|
54
|
+
ignoringTypeImports(): Self;
|
|
55
|
+
/**
|
|
56
|
+
* The imports from these files of these targets are not seen: one known import that is
|
|
57
|
+
* allowed. Files match as the files of a rule do; targets as in `mustNotImport`. For a
|
|
58
|
+
* cycle or a chain, the import is no edge.
|
|
59
|
+
*
|
|
60
|
+
* An exemption that lets no import pass is an error, so it goes when the code that it
|
|
61
|
+
* allows goes. Call this again for more exemptions.
|
|
62
|
+
*/
|
|
63
|
+
ignoringImport(from: AnyOf<string>, to: AnyOf): Self;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gives a class the methods of {@link Filters}. The class says that it has them with
|
|
67
|
+
* `interface Name extends Filters<Name> {}`.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export declare function addFilters(target: {
|
|
72
|
+
readonly prototype: Filtered<unknown>;
|
|
73
|
+
}): void;
|
package/dist/filters.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { filePattern, targetPattern } from "./patterns.js";
|
|
2
|
+
const about = (apply) => ({ aboutImports: true, apply });
|
|
3
|
+
/** The methods of {@link Filters}, written once for every class that has them. */
|
|
4
|
+
const methods = {
|
|
5
|
+
because(reason) {
|
|
6
|
+
return this.filtered({ aboutImports: false, apply: (rule) => ({ ...rule, reason }) });
|
|
7
|
+
},
|
|
8
|
+
asWarning() {
|
|
9
|
+
return this.filtered({ aboutImports: false, apply: (rule) => ({ ...rule, severity: "warning" }) });
|
|
10
|
+
},
|
|
11
|
+
onlyKinds(...kinds) {
|
|
12
|
+
return this.filtered(about((rule) => ({ ...rule, kinds: { type: "only", kinds } })));
|
|
13
|
+
},
|
|
14
|
+
ignoringKinds(...kinds) {
|
|
15
|
+
return this.filtered(about((rule) => ({ ...rule, kinds: { type: "except", kinds } })));
|
|
16
|
+
},
|
|
17
|
+
ignoringCandidates() {
|
|
18
|
+
return this.filtered(about((rule) => ({ ...rule, candidates: "ignore" })));
|
|
19
|
+
},
|
|
20
|
+
ignoringTypeImports() {
|
|
21
|
+
return this.filtered(about((rule) => ({ ...rule, kinds: { type: "except", kinds: ["import-type", "export-type-from"] } })));
|
|
22
|
+
},
|
|
23
|
+
ignoringImport(from, to) {
|
|
24
|
+
const exemption = { from: from.patterns.map(filePattern), to: to.patterns.map(targetPattern) };
|
|
25
|
+
return this.filtered(about((rule) => ({ ...rule, exemptions: [...(rule.exemptions ?? []), exemption] })));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Gives a class the methods of {@link Filters}. The class says that it has them with
|
|
30
|
+
* `interface Name extends Filters<Name> {}`.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export function addFilters(target) {
|
|
35
|
+
Object.defineProperties(target.prototype, Object.getOwnPropertyDescriptors(methods));
|
|
36
|
+
for (const name of Object.keys(methods)) {
|
|
37
|
+
Object.defineProperty(target.prototype, name, { enumerable: false });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switches for the tests of this package. Each one makes a short way of the front end also
|
|
3
|
+
* take the long way, and a difference between the two is an error.
|
|
4
|
+
*
|
|
5
|
+
* - `aliases`: the first step of an alias is read from the exports of the module. With the
|
|
6
|
+
* switch, it is compared with the answer of the compiler for that name.
|
|
7
|
+
* - `walk`: a node whose text has no word that can name a module is not visited. With the
|
|
8
|
+
* switch, all nodes are also visited, and the two visits must find the same.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare const checks: {
|
|
13
|
+
aliases: boolean;
|
|
14
|
+
walk: boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switches for the tests of this package. Each one makes a short way of the front end also
|
|
3
|
+
* take the long way, and a difference between the two is an error.
|
|
4
|
+
*
|
|
5
|
+
* - `aliases`: the first step of an alias is read from the exports of the module. With the
|
|
6
|
+
* switch, it is compared with the answer of the compiler for that name.
|
|
7
|
+
* - `walk`: a node whose text has no word that can name a module is not visited. With the
|
|
8
|
+
* switch, all nodes are also visited, and the two visits must find the same.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const checks = { aliases: false, walk: false };
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
import { COMPONENT_EXTENSIONS } from "./components.js";
|
|
10
|
+
/** Loads the compiler of the installed TypeScript 6. */
|
|
11
|
+
export async function loadClassic(installed) {
|
|
12
|
+
const ts = (await import(__rewriteRelativeImportExtension(installed.packageName))).default;
|
|
13
|
+
return {
|
|
14
|
+
SyntaxKind: ts.SyntaxKind,
|
|
15
|
+
jsDocBesideChildren: true,
|
|
16
|
+
openProjects: (_root, configFiles, files) => ({
|
|
17
|
+
projects: configFiles.map((configFile) => open(ts, configFile, files)),
|
|
18
|
+
// The compiler is in this process, so there is nothing to stop.
|
|
19
|
+
close() { },
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Opens one tsconfig file as a program. */
|
|
24
|
+
function open(ts, configFile, files) {
|
|
25
|
+
const system = files === undefined ? ts.sys : withComponents(ts.sys, files);
|
|
26
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configFile, undefined, {
|
|
27
|
+
...system,
|
|
28
|
+
onUnRecoverableConfigFileDiagnostic(diagnostic) {
|
|
29
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
30
|
+
throw new Error(`refpact: the TypeScript compiler did not load ${configFile}: ${message}`);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
if (parsed === undefined) {
|
|
34
|
+
throw new Error(`refpact: the TypeScript compiler did not load ${configFile}`);
|
|
35
|
+
}
|
|
36
|
+
const host = ts.createCompilerHost(parsed.options, true);
|
|
37
|
+
host.readFile = system.readFile;
|
|
38
|
+
host.fileExists = system.fileExists;
|
|
39
|
+
// An import of a referenced project leads to its source, as with TypeScript 7, and not to
|
|
40
|
+
// the declaration files of a build that may not exist.
|
|
41
|
+
host.useSourceOfProjectReferenceRedirect = () => true;
|
|
42
|
+
const program = ts.createProgram({
|
|
43
|
+
rootNames: parsed.fileNames,
|
|
44
|
+
options: parsed.options,
|
|
45
|
+
host,
|
|
46
|
+
...(parsed.projectReferences === undefined ? {} : { projectReferences: parsed.projectReferences }),
|
|
47
|
+
});
|
|
48
|
+
const checker = program.getTypeChecker();
|
|
49
|
+
const Flags = ts.SymbolFlags;
|
|
50
|
+
const memberFlags = Flags.Property | Flags.Method | Flags.GetAccessor | Flags.SetAccessor | Flags.EnumMember;
|
|
51
|
+
const ownerFlags = Flags.Class | Flags.Interface | Flags.RegularEnum | Flags.ConstEnum;
|
|
52
|
+
const native = (symbol) => symbol;
|
|
53
|
+
return {
|
|
54
|
+
configFile,
|
|
55
|
+
options: parsed.options,
|
|
56
|
+
ownFiles: program
|
|
57
|
+
.getSourceFiles()
|
|
58
|
+
.filter((file) => !program.isSourceFileDefaultLibrary(file) && !program.isSourceFileFromExternalLibrary(file))
|
|
59
|
+
.map((file) => file.fileName),
|
|
60
|
+
sourceFile: (file) => program.getSourceFile(file),
|
|
61
|
+
// The program holds its syntax trees for as long as it lives.
|
|
62
|
+
forgetSourceFiles() { },
|
|
63
|
+
symbolsAt: (nodes) => nodes.map((node) => known(checker, checker.getSymbolAtLocation(node))),
|
|
64
|
+
shorthandValueAt: (property) => known(checker, checker.getShorthandAssignmentValueSymbol(property)),
|
|
65
|
+
filesOf: (symbol) => (native(symbol).declarations ?? []).map((declaration) => declaration.getSourceFile().fileName),
|
|
66
|
+
same: (a, b) => a === b,
|
|
67
|
+
ownerOf(symbol) {
|
|
68
|
+
if ((native(symbol).flags & memberFlags) === 0) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
// A member is declared inside its owner, and the name of the owner has its symbol.
|
|
72
|
+
const name = native(symbol).declarations?.[0]?.parent?.name;
|
|
73
|
+
const owner = name === undefined ? undefined : checker.getSymbolAtLocation(name);
|
|
74
|
+
return owner !== undefined && (owner.flags & ownerFlags) !== 0 ? owner : undefined;
|
|
75
|
+
},
|
|
76
|
+
isTypeParameter: (symbol) => (native(symbol).flags & Flags.TypeParameter) !== 0,
|
|
77
|
+
immediateAlias: (symbol) => (native(symbol).flags & Flags.Alias) === 0 ? undefined : known(checker, checker.getImmediateAliasedSymbol(native(symbol))),
|
|
78
|
+
exportsOf: (module) => new Map(checker.getExportsOfModule(native(module)).map((symbol) => [symbol.name, symbol])),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** The symbol, when the compiler found what it stands for. */
|
|
82
|
+
function known(checker, symbol) {
|
|
83
|
+
return symbol === undefined || checker.isUnknownSymbol(symbol) ? undefined : symbol;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The file system of the compiler with the component files in it. A component file
|
|
87
|
+
* `App.svelte` is there as `App.svelte.ts`, as `components.ts` describes.
|
|
88
|
+
*/
|
|
89
|
+
function withComponents(system, files) {
|
|
90
|
+
return {
|
|
91
|
+
...system,
|
|
92
|
+
readFile: (fileName, encoding) => files.readFile(fileName) ?? system.readFile(fileName, encoding),
|
|
93
|
+
fileExists: (fileName) => files.fileExists(fileName) ?? system.fileExists(fileName),
|
|
94
|
+
// The files of a tsconfig. A pattern that names `.ts` files names the component files
|
|
95
|
+
// too, under the names that they are shown with.
|
|
96
|
+
readDirectory(directory, extensions, excludes, includes, depth) {
|
|
97
|
+
const found = system.readDirectory(directory, extensions, excludes, includes, depth);
|
|
98
|
+
const components = system
|
|
99
|
+
.readDirectory(directory, COMPONENT_EXTENSIONS, excludes, includes?.map((pattern) => pattern.replace(/\.ts$/, "")), depth)
|
|
100
|
+
.map((file) => `${file}.ts`)
|
|
101
|
+
.filter((file) => files.fileExists(file) === true);
|
|
102
|
+
return [...found, ...components];
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ComponentFiles } from "./components.ts";
|
|
2
|
+
/** A node of a syntax tree. */
|
|
3
|
+
export interface Node {
|
|
4
|
+
readonly kind: number;
|
|
5
|
+
/** Where the node begins in the text of the file, with the comments before it. */
|
|
6
|
+
readonly pos: number;
|
|
7
|
+
/** The position after the last character of the node in the text of the file. */
|
|
8
|
+
readonly end: number;
|
|
9
|
+
forEachChild(visit: (child: Node) => void): void;
|
|
10
|
+
getStart(sourceFile: SourceFile): number;
|
|
11
|
+
}
|
|
12
|
+
export interface SourceFile extends Node {
|
|
13
|
+
/** The text of the file. The positions of nodes count in it. */
|
|
14
|
+
readonly text: string;
|
|
15
|
+
getLineAndCharacterOfPosition(position: number): {
|
|
16
|
+
readonly line: number;
|
|
17
|
+
readonly character: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** A symbol of the compiler. Only its name is read directly; the project answers the rest. */
|
|
21
|
+
export interface TsSymbol {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
}
|
|
24
|
+
/** One opened tsconfig project. */
|
|
25
|
+
export interface CompilerProject {
|
|
26
|
+
readonly configFile: string;
|
|
27
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
28
|
+
/** Absolute names of the files of the program, without default libraries and external packages. */
|
|
29
|
+
readonly ownFiles: readonly string[];
|
|
30
|
+
sourceFile(file: string): SourceFile | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Lets go of the syntax trees that `sourceFile` gave, so that their memory can be used
|
|
33
|
+
* again. The trees that the caller still holds stay valid.
|
|
34
|
+
*/
|
|
35
|
+
forgetSourceFiles(): void;
|
|
36
|
+
/**
|
|
37
|
+
* The symbols at many nodes: `undefined` where the compiler finds none. With the native
|
|
38
|
+
* compiler this is one round trip.
|
|
39
|
+
*/
|
|
40
|
+
symbolsAt(nodes: readonly Node[]): (TsSymbol | undefined)[];
|
|
41
|
+
/** The symbol of the value of a shorthand property: `fetch` in `{ fetch }`. One round trip. */
|
|
42
|
+
shorthandValueAt(property: Node): TsSymbol | undefined;
|
|
43
|
+
/** Absolute names of the files that declare the symbol. */
|
|
44
|
+
filesOf(symbol: TsSymbol): string[];
|
|
45
|
+
/** True when the two are the same symbol. */
|
|
46
|
+
same(a: TsSymbol, b: TsSymbol): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The class, the interface or the enum that declares a member: a property, a method, an
|
|
49
|
+
* accessor or an enum member. `undefined` for other symbols, and for a member of a type
|
|
50
|
+
* without a name.
|
|
51
|
+
*/
|
|
52
|
+
ownerOf(symbol: TsSymbol): TsSymbol | undefined;
|
|
53
|
+
/** True when the symbol is a type parameter, as `T` in `function first<T>(items: T[]): T`. */
|
|
54
|
+
isTypeParameter(symbol: TsSymbol): boolean;
|
|
55
|
+
/** One step from an alias to what it stands for. `undefined` when the symbol is not an alias. */
|
|
56
|
+
immediateAlias(symbol: TsSymbol): TsSymbol | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* All that a module exports, also through `export *`, by name. With the native compiler
|
|
59
|
+
* this is one round trip for each module.
|
|
60
|
+
*/
|
|
61
|
+
exportsOf(module: TsSymbol): ReadonlyMap<string, TsSymbol>;
|
|
62
|
+
}
|
|
63
|
+
export interface CompilerSession {
|
|
64
|
+
readonly projects: readonly CompilerProject[];
|
|
65
|
+
close(): void;
|
|
66
|
+
}
|
|
67
|
+
/** What a backend gives. */
|
|
68
|
+
export interface Backend {
|
|
69
|
+
/** The numbers of the kinds of nodes. They differ between versions of the compiler. */
|
|
70
|
+
readonly SyntaxKind: Readonly<Record<string, number>>;
|
|
71
|
+
/**
|
|
72
|
+
* Where the nodes of a JSDoc comment are. `true`: in the field `jsDoc` of the node after
|
|
73
|
+
* the comment, as in TypeScript 6. `false`: among the children, as in TypeScript 7.
|
|
74
|
+
*/
|
|
75
|
+
readonly jsDocBesideChildren: boolean;
|
|
76
|
+
openProjects(root: string, configFiles: readonly string[], files?: ComponentFiles): CompilerSession;
|
|
77
|
+
}
|
|
78
|
+
/** The installed `typescript` package. */
|
|
79
|
+
export interface Installed {
|
|
80
|
+
readonly packageName: string;
|
|
81
|
+
readonly version: string;
|
|
82
|
+
readonly exports: Readonly<Record<string, unknown>>;
|
|
83
|
+
}
|
|
84
|
+
/** The version of the compiler that reads the projects, such as `7.0.2`. */
|
|
85
|
+
export declare const compilerVersion: string;
|
|
86
|
+
declare const KIND_NAMES: readonly ["CallExpression", "CatchClause", "AsExpression", "BinaryExpression", "ConditionalExpression", "NewExpression", "NonNullExpression", "ParenthesizedExpression", "PlusToken", "SatisfiesExpression", "TemplateExpression", "ArrayLiteralExpression", "AbstractKeyword", "ArrayBindingPattern", "ArrowFunction", "AsyncKeyword", "Block", "BindingElement", "ClassDeclaration", "ConstKeyword", "Constructor", "DebuggerStatement", "DeclareKeyword", "Decorator", "DefaultKeyword", "EnumMember", "EnumDeclaration", "ExportKeyword", "ExtendsKeyword", "ForInStatement", "ForOfStatement", "ForStatement", "FunctionDeclaration", "FunctionExpression", "GetAccessor", "HeritageClause", "ImplementsKeyword", "InterfaceDeclaration", "ModuleDeclaration", "TypeAliasDeclaration", "VariableStatement", "ExportDeclaration", "ExternalModuleReference", "Identifier", "ImportClause", "ImportDeclaration", "ImportEqualsDeclaration", "ImportKeyword", "ImportSpecifier", "ImportType", "MethodDeclaration", "JsxAttribute", "JsxClosingElement", "JsxOpeningElement", "JsxSelfClosingElement", "MetaProperty", "NamedExports", "NamedImports", "NamespaceImport", "NoSubstitutionTemplateLiteral", "ObjectBindingPattern", "Parameter", "PropertyAssignment", "PropertyDeclaration", "PropertySignature", "PropertyAccessExpression", "QualifiedName", "SetAccessor", "ShorthandPropertyAssignment", "StringLiteral", "TypeKeyword", "TypeQuery", "TypeReference", "VariableDeclaration", "VariableDeclarationList"];
|
|
87
|
+
/** See {@link Backend.jsDocBesideChildren}. */
|
|
88
|
+
export declare const jsDocBesideChildren: boolean;
|
|
89
|
+
export type SyntaxKind = number;
|
|
90
|
+
export declare const SyntaxKind: Readonly<Record<(typeof KIND_NAMES)[number], number>>;
|
|
91
|
+
/** Opens the given tsconfig files with the installed compiler. */
|
|
92
|
+
export declare function openProjects(root: string, configFiles: readonly string[], files?: ComponentFiles): CompilerSession;
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the front end needs from a TypeScript compiler.
|
|
3
|
+
*
|
|
4
|
+
* The installed `typescript` package is the compiler, so a project is read by the same
|
|
5
|
+
* compiler that builds it. `native.ts` talks to the native compiler of TypeScript 7 through
|
|
6
|
+
* the client that the package ships. `classic.ts` runs the compiler of TypeScript 6 in this
|
|
7
|
+
* process.
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { loadClassic } from "./classic.js";
|
|
11
|
+
import { loadNative } from "./native.js";
|
|
12
|
+
// REFPACT_TYPESCRIPT names the package to load instead of `typescript`. It is a switch for
|
|
13
|
+
// tests and development only, to run the tests against a second compiler that is installed
|
|
14
|
+
// under an alias, for example `REFPACT_TYPESCRIPT=typescript-6`.
|
|
15
|
+
const packageName = process.env["REFPACT_TYPESCRIPT"] || "typescript";
|
|
16
|
+
const manifest = createRequire(import.meta.url)(`${packageName}/package.json`);
|
|
17
|
+
const installed = { packageName, version: manifest.version, exports: manifest.exports ?? {} };
|
|
18
|
+
/** The version of the compiler that reads the projects, such as `7.0.2`. */
|
|
19
|
+
export const compilerVersion = installed.version;
|
|
20
|
+
const major = Number(installed.version.split(".")[0]);
|
|
21
|
+
if (major !== 6 && major !== 7) {
|
|
22
|
+
throw new Error(`refpact: TypeScript ${installed.version} is not supported. Install TypeScript 6 or 7.`);
|
|
23
|
+
}
|
|
24
|
+
const backend = major === 6 ? await loadClassic(installed) : await loadNative(installed);
|
|
25
|
+
// The kinds that the front end reads. A compiler that does not have one of them is not one
|
|
26
|
+
// that this code was written for, so that is an error here and not a silent miss later.
|
|
27
|
+
const KIND_NAMES = [
|
|
28
|
+
"CallExpression",
|
|
29
|
+
"CatchClause",
|
|
30
|
+
"AsExpression",
|
|
31
|
+
"BinaryExpression",
|
|
32
|
+
"ConditionalExpression",
|
|
33
|
+
"NewExpression",
|
|
34
|
+
"NonNullExpression",
|
|
35
|
+
"ParenthesizedExpression",
|
|
36
|
+
"PlusToken",
|
|
37
|
+
"SatisfiesExpression",
|
|
38
|
+
"TemplateExpression",
|
|
39
|
+
"ArrayLiteralExpression",
|
|
40
|
+
"AbstractKeyword",
|
|
41
|
+
"ArrayBindingPattern",
|
|
42
|
+
"ArrowFunction",
|
|
43
|
+
"AsyncKeyword",
|
|
44
|
+
"Block",
|
|
45
|
+
"BindingElement",
|
|
46
|
+
"ClassDeclaration",
|
|
47
|
+
"ConstKeyword",
|
|
48
|
+
"Constructor",
|
|
49
|
+
"DebuggerStatement",
|
|
50
|
+
"DeclareKeyword",
|
|
51
|
+
"Decorator",
|
|
52
|
+
"DefaultKeyword",
|
|
53
|
+
"EnumMember",
|
|
54
|
+
"EnumDeclaration",
|
|
55
|
+
"ExportKeyword",
|
|
56
|
+
"ExtendsKeyword",
|
|
57
|
+
"ForInStatement",
|
|
58
|
+
"ForOfStatement",
|
|
59
|
+
"ForStatement",
|
|
60
|
+
"FunctionDeclaration",
|
|
61
|
+
"FunctionExpression",
|
|
62
|
+
"GetAccessor",
|
|
63
|
+
"HeritageClause",
|
|
64
|
+
"ImplementsKeyword",
|
|
65
|
+
"InterfaceDeclaration",
|
|
66
|
+
"ModuleDeclaration",
|
|
67
|
+
"TypeAliasDeclaration",
|
|
68
|
+
"VariableStatement",
|
|
69
|
+
"ExportDeclaration",
|
|
70
|
+
"ExternalModuleReference",
|
|
71
|
+
"Identifier",
|
|
72
|
+
"ImportClause",
|
|
73
|
+
"ImportDeclaration",
|
|
74
|
+
"ImportEqualsDeclaration",
|
|
75
|
+
"ImportKeyword",
|
|
76
|
+
"ImportSpecifier",
|
|
77
|
+
"ImportType",
|
|
78
|
+
"MethodDeclaration",
|
|
79
|
+
"JsxAttribute",
|
|
80
|
+
"JsxClosingElement",
|
|
81
|
+
"JsxOpeningElement",
|
|
82
|
+
"JsxSelfClosingElement",
|
|
83
|
+
"MetaProperty",
|
|
84
|
+
"NamedExports",
|
|
85
|
+
"NamedImports",
|
|
86
|
+
"NamespaceImport",
|
|
87
|
+
"NoSubstitutionTemplateLiteral",
|
|
88
|
+
"ObjectBindingPattern",
|
|
89
|
+
"Parameter",
|
|
90
|
+
"PropertyAssignment",
|
|
91
|
+
"PropertyDeclaration",
|
|
92
|
+
"PropertySignature",
|
|
93
|
+
"PropertyAccessExpression",
|
|
94
|
+
"QualifiedName",
|
|
95
|
+
"SetAccessor",
|
|
96
|
+
"ShorthandPropertyAssignment",
|
|
97
|
+
"StringLiteral",
|
|
98
|
+
"TypeKeyword",
|
|
99
|
+
"TypeQuery",
|
|
100
|
+
"TypeReference",
|
|
101
|
+
"VariableDeclaration",
|
|
102
|
+
"VariableDeclarationList",
|
|
103
|
+
];
|
|
104
|
+
/** See {@link Backend.jsDocBesideChildren}. */
|
|
105
|
+
export const jsDocBesideChildren = backend.jsDocBesideChildren;
|
|
106
|
+
export const SyntaxKind = Object.fromEntries(KIND_NAMES.map((name) => {
|
|
107
|
+
const value = backend.SyntaxKind[name];
|
|
108
|
+
if (typeof value !== "number") {
|
|
109
|
+
throw new Error(`refpact: TypeScript ${installed.version} has no SyntaxKind.${name}`);
|
|
110
|
+
}
|
|
111
|
+
return [name, value];
|
|
112
|
+
}));
|
|
113
|
+
/** Opens the given tsconfig files with the installed compiler. */
|
|
114
|
+
export function openProjects(root, configFiles, files) {
|
|
115
|
+
return backend.openProjects(root, configFiles, files);
|
|
116
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The extensions of the component files that are read. */
|
|
2
|
+
export declare const COMPONENT_EXTENSIONS: readonly ComponentExtension[];
|
|
3
|
+
/** The extension of a kind of component file that refpact reads. */
|
|
4
|
+
export type ComponentExtension = ".svelte" | ".vue" | ".astro";
|
|
5
|
+
/** What the compiler asks of the file system. `undefined` lets the compiler read the disk. */
|
|
6
|
+
export interface ComponentFiles {
|
|
7
|
+
readFile(fileName: string): string | undefined;
|
|
8
|
+
fileExists(fileName: string): boolean | undefined;
|
|
9
|
+
getAccessibleEntries(directoryName: string): {
|
|
10
|
+
files: string[];
|
|
11
|
+
directories: string[];
|
|
12
|
+
} | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The text of a component file with a space in place of each character that is not code: all
|
|
16
|
+
* that is outside its `<script>` blocks, and outside the part between `---` lines at the
|
|
17
|
+
* start of an Astro file. Line breaks stay.
|
|
18
|
+
*/
|
|
19
|
+
export declare function scriptsOf(text: string, extension: ComponentExtension): string;
|
|
20
|
+
/** The component file that a file name of the compiler stands for: `App.svelte` for `App.svelte.ts`. */
|
|
21
|
+
export declare function componentOf(fileName: string, extensions: readonly ComponentExtension[]): {
|
|
22
|
+
readonly file: string;
|
|
23
|
+
readonly extension: ComponentExtension;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The file system that shows the component files to the compiler. A file that is on the
|
|
27
|
+
* disk under the name, such as a module `state.svelte.ts` of Svelte, is read from the disk.
|
|
28
|
+
*/
|
|
29
|
+
export declare function componentFiles(extensions: readonly ComponentExtension[]): ComponentFiles;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component files, such as `App.svelte` and `App.vue`: a file of markup that holds its code
|
|
3
|
+
* in `<script>` blocks.
|
|
4
|
+
*
|
|
5
|
+
* The compiler reads TypeScript only. So each component file is shown to it as a file with
|
|
6
|
+
* `.ts` after its name, `App.svelte.ts`, whose text is that of the component with a space in
|
|
7
|
+
* place of each character outside the scripts. The code is then at the line and the column
|
|
8
|
+
* that it has in the component, and an import of `./App.svelte` leads to the file.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
/** The extensions of the component files that are read. */
|
|
13
|
+
export const COMPONENT_EXTENSIONS = [".svelte", ".vue", ".astro"];
|
|
14
|
+
/** The types of the scripts that are code of the component. Other scripts, such as JSON, are data. */
|
|
15
|
+
const CODE_TYPES = new Set(["", "module", "text/javascript", "text/typescript", "ts", "js", "typescript", "javascript"]);
|
|
16
|
+
/**
|
|
17
|
+
* The text of a component file with a space in place of each character that is not code: all
|
|
18
|
+
* that is outside its `<script>` blocks, and outside the part between `---` lines at the
|
|
19
|
+
* start of an Astro file. Line breaks stay.
|
|
20
|
+
*/
|
|
21
|
+
export function scriptsOf(text, extension) {
|
|
22
|
+
const keep = [];
|
|
23
|
+
if (extension === ".astro") {
|
|
24
|
+
const front = /^\s*---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
25
|
+
if (front !== null) {
|
|
26
|
+
const start = front.index + front[0].indexOf(front[1], front[0].indexOf("---") + 3);
|
|
27
|
+
keep.push([start, start + front[1].length]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const block = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
|
31
|
+
for (let found = block.exec(text); found !== null; found = block.exec(text)) {
|
|
32
|
+
const attributes = found[1];
|
|
33
|
+
const type = /\btype\s*=\s*["']?([^"'\s>]*)/i.exec(attributes)?.[1] ?? "";
|
|
34
|
+
if (/\bsrc\s*=/i.test(attributes) || !CODE_TYPES.has(type.toLowerCase())) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const start = found.index + found[0].indexOf(">") + 1;
|
|
38
|
+
keep.push([start, start + found[2].length]);
|
|
39
|
+
}
|
|
40
|
+
let code = "";
|
|
41
|
+
let at = 0;
|
|
42
|
+
for (const [start, end] of keep.sort((a, b) => a[0] - b[0])) {
|
|
43
|
+
if (start < at) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
code += text.slice(at, start).replace(/[^\r\n]/g, " ") + text.slice(start, end);
|
|
47
|
+
at = end;
|
|
48
|
+
}
|
|
49
|
+
return code + text.slice(at).replace(/[^\r\n]/g, " ");
|
|
50
|
+
}
|
|
51
|
+
/** The component file that a file name of the compiler stands for: `App.svelte` for `App.svelte.ts`. */
|
|
52
|
+
export function componentOf(fileName, extensions) {
|
|
53
|
+
if (!fileName.endsWith(".ts")) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const file = fileName.slice(0, -".ts".length);
|
|
57
|
+
const extension = extensions.find((known) => file.endsWith(known));
|
|
58
|
+
return extension === undefined ? undefined : { file, extension };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The file system that shows the component files to the compiler. A file that is on the
|
|
62
|
+
* disk under the name, such as a module `state.svelte.ts` of Svelte, is read from the disk.
|
|
63
|
+
*/
|
|
64
|
+
export function componentFiles(extensions) {
|
|
65
|
+
const isFile = (file) => {
|
|
66
|
+
try {
|
|
67
|
+
return statSync(file).isFile();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const shown = (fileName) => {
|
|
74
|
+
const component = componentOf(fileName, extensions);
|
|
75
|
+
return component !== undefined && !existsSync(fileName) && isFile(component.file) ? component : undefined;
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
readFile(fileName) {
|
|
79
|
+
const component = shown(fileName);
|
|
80
|
+
return component === undefined ? undefined : scriptsOf(readFileSync(component.file, "utf8"), component.extension);
|
|
81
|
+
},
|
|
82
|
+
fileExists: (fileName) => (shown(fileName) !== undefined ? true : undefined),
|
|
83
|
+
getAccessibleEntries(directoryName) {
|
|
84
|
+
let names;
|
|
85
|
+
try {
|
|
86
|
+
names = readdirSync(directoryName);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (!names.some((name) => extensions.some((extension) => name.endsWith(extension)))) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const files = [];
|
|
95
|
+
const directories = [];
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
const entry = path.join(directoryName, name);
|
|
98
|
+
if (isFile(entry)) {
|
|
99
|
+
files.push(name);
|
|
100
|
+
if (extensions.some((extension) => name.endsWith(extension)) && !names.includes(`${name}.ts`)) {
|
|
101
|
+
files.push(`${name}.ts`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (existsSync(entry)) {
|
|
105
|
+
directories.push(name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { files, directories };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Node, SourceFile } from "./compiler.ts";
|
|
2
|
+
/** A kind of item: `function`, `class`, `interface`, `type`, `enum`, `variable` or `namespace`. */
|
|
3
|
+
export type ItemKind = "function" | "class" | "interface" | "type" | "enum" | "variable" | "namespace" | "package";
|
|
4
|
+
/** The kinds of item a declaration can have. */
|
|
5
|
+
export declare const ITEM_KINDS: readonly ItemKind[];
|
|
6
|
+
/** `exported`: the module exports the item, also under another name. `local`: it does not. */
|
|
7
|
+
export type Visibility = "exported" | "local";
|
|
8
|
+
/** The visibilities a declaration can have. */
|
|
9
|
+
export declare const VISIBILITIES: readonly Visibility[];
|
|
10
|
+
/**
|
|
11
|
+
* The relations that the syntax of a declaration gives: `extends` and `implements` of a class
|
|
12
|
+
* or an interface, the decorators of a class, and the types that a function takes and
|
|
13
|
+
* returns and that a variable has.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TIES: readonly ["extends", "implements", "decorator", "returns", "takes", "has-type"];
|
|
16
|
+
/**
|
|
17
|
+
* What a declaration says of its item besides its kind and its visibility: `async`,
|
|
18
|
+
* `abstract`, `declare`, `const` (a `const` variable or a `const enum`), `default` (an
|
|
19
|
+
* `export default` function or class), and `documented` (a JSDoc comment before it).
|
|
20
|
+
*/
|
|
21
|
+
export type Modifier = "async" | "abstract" | "declare" | "const" | "default" | "documented";
|
|
22
|
+
/** The modifiers a declaration can have, in the order of their description. */
|
|
23
|
+
export declare const MODIFIERS: readonly Modifier[];
|
|
24
|
+
/** All relations of a declaration: the ties, and the types that it extends or implements at any depth. */
|
|
25
|
+
export declare const RELATIONS: readonly ["extends", "implements", "decorator", "returns", "takes", "has-type", "assignable-to"];
|
|
26
|
+
/** One tie of a declaration, by the node that names its target. */
|
|
27
|
+
export interface Tie {
|
|
28
|
+
readonly relation: (typeof TIES)[number];
|
|
29
|
+
readonly node: Node;
|
|
30
|
+
}
|
|
31
|
+
/** A top-level declaration of a file, before its names are resolved. */
|
|
32
|
+
export interface Declared {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly kind: ItemKind;
|
|
35
|
+
/** The node of the name, or of the declaration when it has no name. */
|
|
36
|
+
readonly at: Node;
|
|
37
|
+
/** The node of the whole declaration: the item ends where it ends. */
|
|
38
|
+
readonly whole: Node;
|
|
39
|
+
/** True when the declaration has the `export` modifier. */
|
|
40
|
+
readonly exported: boolean;
|
|
41
|
+
readonly ties: readonly Tie[];
|
|
42
|
+
readonly modifiers: readonly Modifier[];
|
|
43
|
+
}
|
|
44
|
+
/** The top-level declarations of a file, in the order of the text. */
|
|
45
|
+
export declare function collectDeclarations(sourceFile: SourceFile): Declared[];
|