dependency-cruiser 18.6.0 → 18.8.0-beta-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.
- package/configs/rules/no-duplicate-dependency-types.cjs +1 -1
- package/package.json +1 -1
- package/src/analyze/summarize/index.mjs +1 -102
- package/src/analyze/summarize/verify-environment.mjs +146 -0
- package/src/cli/init-config/config-template.mjs +1 -1
- package/src/config-utl/extract-ts-config.mjs +4 -44
- package/src/extract/resolve/determine-dependency-types.mjs +3 -1
- package/src/extract/swc/dependency-visitor.mjs +4 -2
- package/src/extract/transpile/meta.mjs +16 -5
- package/src/extract/transpile/typescript-wrap.mjs +5 -11
- package/src/extract/tsc/extract-typescript-deps.mjs +40 -59
- package/src/extract/tsc/parse.mjs +1 -9
- package/src/meta.cjs +2 -2
- package/src/utl/typescript-facade.mjs +238 -0
- package/types/config-utl/extract-ts-config.d.mts +26 -2
|
@@ -2,7 +2,7 @@ module.exports = {
|
|
|
2
2
|
name: "no-duplicate-dep-types",
|
|
3
3
|
comment:
|
|
4
4
|
"Likely this module depends on an external ('npm') package that occurs more " +
|
|
5
|
-
"than once in your package.json i.e.
|
|
5
|
+
"than once in your package.json i.e. both as a devDependencies and in dependencies. " +
|
|
6
6
|
"This will cause maintenance problems later on. If it's intentional, you can " +
|
|
7
7
|
"disable this rule by adding this override as a rule in the 'forbidden' section " +
|
|
8
8
|
"of your dependency-cruiser configuration: " +
|
package/package.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EOL } from "node:os";
|
|
2
1
|
import addRuleSetUsed from "./add-rule-set-used.mjs";
|
|
3
2
|
import summarizeModules from "./summarize-modules.mjs";
|
|
4
3
|
import summarizeFolders from "./summarize-folders.mjs";
|
|
@@ -11,6 +10,7 @@ import {
|
|
|
11
10
|
} from "./get-stats.mjs";
|
|
12
11
|
import { compareViolations } from "#graph-utl/compare.mjs";
|
|
13
12
|
import { getEnvironmentInfo } from "#environment.mjs";
|
|
13
|
+
import { determineEnvironmentIssues } from "./verify-environment.mjs";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @import { IOptions, IEnvironmentIssue, ISummary, IModule, IFolder } from "../../../types/cruise-result.mjs"
|
|
@@ -18,107 +18,6 @@ import { getEnvironmentInfo } from "#environment.mjs";
|
|
|
18
18
|
* @import { IStrictCruiseOptions } from "../../../types/strict-options.mjs"
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* @param { IOptions } pOptions
|
|
23
|
-
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
24
|
-
* @returns { boolean }
|
|
25
|
-
*/
|
|
26
|
-
function swcIsConsistent(pOptions, pAvailableTranspilers) {
|
|
27
|
-
const lSWCNeeded = (pOptions?.parser ?? "acorn") === "swc";
|
|
28
|
-
return lSWCNeeded
|
|
29
|
-
? pAvailableTranspilers.some(
|
|
30
|
-
(pTranspiler) => pTranspiler.name === "swc" && pTranspiler.available,
|
|
31
|
-
)
|
|
32
|
-
: true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param { IOptions } pOptions
|
|
37
|
-
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
38
|
-
* @returns { boolean }
|
|
39
|
-
*/
|
|
40
|
-
function typescriptIsConsistent(pOptions, pAvailableTranspilers) {
|
|
41
|
-
const lTypeScriptNeeded =
|
|
42
|
-
Boolean(pOptions?.tsConfig) ||
|
|
43
|
-
Boolean(pOptions?.tsPreCompilationDeps) ||
|
|
44
|
-
Boolean(pOptions?.detectJSDocImports) ||
|
|
45
|
-
(pOptions?.parser ?? "acorn") === "tsc";
|
|
46
|
-
return lTypeScriptNeeded
|
|
47
|
-
? pAvailableTranspilers.some(
|
|
48
|
-
(pTranspiler) =>
|
|
49
|
-
pTranspiler.name === "typescript" && pTranspiler.available,
|
|
50
|
-
)
|
|
51
|
-
: true;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* @param { IOptions } pOptions
|
|
55
|
-
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
56
|
-
* @returns { boolean }
|
|
57
|
-
*/
|
|
58
|
-
function babelIsConsistent(pOptions, pAvailableTranspilers) {
|
|
59
|
-
return Boolean(pOptions?.babelConfig)
|
|
60
|
-
? pAvailableTranspilers.some(
|
|
61
|
-
(pTranspiler) => pTranspiler.name === "babel" && pTranspiler.available,
|
|
62
|
-
)
|
|
63
|
-
: true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @param {IOptions} pOptions
|
|
68
|
-
* @param {IAvailableTranspiler[]} pAvailableTranspilers
|
|
69
|
-
* @returns {{issues?: IEnvironmentIssue[]}}
|
|
70
|
-
*/
|
|
71
|
-
function determineEnvironmentIssues(pOptions, pAvailableTranspilers) {
|
|
72
|
-
const lIssues = [];
|
|
73
|
-
if (!typescriptIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
74
|
-
const tsTranspiler = pAvailableTranspilers.find(
|
|
75
|
-
(pTranspiler) => pTranspiler.name === "typescript",
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
lIssues.push({
|
|
79
|
-
severity: "warn",
|
|
80
|
-
name: "missing-typescript-transpiler",
|
|
81
|
-
description:
|
|
82
|
-
`dependency-cruiser detected a TypeScript environment,${EOL}` +
|
|
83
|
-
` but not a compatible TypeScript compiler (${tsTranspiler.name}: ${tsTranspiler.version}). This means${EOL}` +
|
|
84
|
-
` it's likely to have missed TypeScript sources and dependencies.${EOL}${EOL}` +
|
|
85
|
-
` Install typescript to get better results (e.g. npm i -D typescript@^6).${EOL}${EOL}` +
|
|
86
|
-
` => Support for typescript@>=7 will follow when its API is published and stable.${EOL}`,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (!swcIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
90
|
-
const swcTranspiler = pAvailableTranspilers.find(
|
|
91
|
-
(pTranspiler) => pTranspiler.name === "swc",
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
lIssues.push({
|
|
95
|
-
severity: "warn",
|
|
96
|
-
name: "missing-swc-transpiler",
|
|
97
|
-
description:
|
|
98
|
-
`dependency-cruiser found swc configured as parser, but ${EOL}` +
|
|
99
|
-
` it did not find a compatible version of swc (${swcTranspiler.name}: ${swcTranspiler.version}).${EOL}` +
|
|
100
|
-
` dependency-cruiser might have missed some sources or dependencies.${EOL}${EOL}` +
|
|
101
|
-
` Install swc (e.g. npm i -D @swc/core) to get better results.${EOL}`,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
if (!babelIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
105
|
-
const babelTranspiler = pAvailableTranspilers.find(
|
|
106
|
-
(pTranspiler) => pTranspiler.name === "babel",
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
lIssues.push({
|
|
110
|
-
severity: "warn",
|
|
111
|
-
name: "missing-babel-transpiler",
|
|
112
|
-
description:
|
|
113
|
-
`dependency-cruiser detected a babel configuration, but not a${EOL}` +
|
|
114
|
-
` compatible babel transpiler (${babelTranspiler.name}: ${babelTranspiler.version}). If you indeed use babel,${EOL}` +
|
|
115
|
-
` dependency-cruiser might have missed some sources or dependencies.${EOL}${EOL}` +
|
|
116
|
-
` Install babel (e.g. npm i -D @babel/core@^7) to get better results.${EOL}`,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return lIssues.length > 0 ? { issues: lIssues } : {};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
21
|
function getAdvisedExitCode(
|
|
123
22
|
pErrorCount,
|
|
124
23
|
pBaselineStaleCount,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { EOL } from "node:os";
|
|
2
|
+
/**
|
|
3
|
+
* @import { IOptions, IEnvironmentIssue } from "../../../types/cruise-result.mjs"
|
|
4
|
+
* @import { IAvailableTranspiler } from "../../../types/environment.mjs"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param { IOptions } pOptions
|
|
9
|
+
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
10
|
+
* @returns { boolean }
|
|
11
|
+
*/
|
|
12
|
+
function swcIsConsistent(pOptions, pAvailableTranspilers) {
|
|
13
|
+
const lSWCNeeded = (pOptions?.parser ?? "acorn") === "swc";
|
|
14
|
+
return lSWCNeeded
|
|
15
|
+
? pAvailableTranspilers.some(
|
|
16
|
+
(pTranspiler) => pTranspiler.name === "swc" && pTranspiler.available,
|
|
17
|
+
)
|
|
18
|
+
: true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param { IOptions } pOptions
|
|
23
|
+
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
24
|
+
* @returns { boolean }
|
|
25
|
+
*/
|
|
26
|
+
function tscIsConsistent(pOptions, pAvailableTranspilers) {
|
|
27
|
+
const lTSCNeeded =
|
|
28
|
+
Boolean(pOptions?.tsPreCompilationDeps) ||
|
|
29
|
+
Boolean(pOptions?.detectJSDocImports) ||
|
|
30
|
+
(pOptions?.parser ?? "acorn") === "tsc";
|
|
31
|
+
return lTSCNeeded
|
|
32
|
+
? pAvailableTranspilers.some(
|
|
33
|
+
(pTranspiler) =>
|
|
34
|
+
pTranspiler.name === "typescript" && pTranspiler.available,
|
|
35
|
+
)
|
|
36
|
+
: true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function typeScriptIsConsistent(pOptions, pAvailableTranspilers) {
|
|
40
|
+
const lTypeScriptNeeded =
|
|
41
|
+
Boolean(pOptions?.tsConfig) &&
|
|
42
|
+
!(
|
|
43
|
+
// these two options require tsc (covered in tscIsConsistent), so
|
|
44
|
+
// to prevent typeScriptIsConsistent from also firing (and printing
|
|
45
|
+
// two messages) we exclude them here
|
|
46
|
+
Boolean(pOptions?.tsPreCompilationDeps) ||
|
|
47
|
+
Boolean(pOptions?.detectJSDocImports)
|
|
48
|
+
);
|
|
49
|
+
return lTypeScriptNeeded
|
|
50
|
+
? pAvailableTranspilers.some(
|
|
51
|
+
(pTranspiler) =>
|
|
52
|
+
(pTranspiler.name === "typescript" ||
|
|
53
|
+
(pTranspiler.name === "swc" && pOptions?.parser === "swc")) &&
|
|
54
|
+
pTranspiler.available,
|
|
55
|
+
)
|
|
56
|
+
: true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param { IOptions } pOptions
|
|
61
|
+
* @param { IAvailableTranspiler[] } pAvailableTranspilers
|
|
62
|
+
* @returns { boolean }
|
|
63
|
+
*/
|
|
64
|
+
function babelIsConsistent(pOptions, pAvailableTranspilers) {
|
|
65
|
+
return Boolean(pOptions?.babelConfig)
|
|
66
|
+
? pAvailableTranspilers.some(
|
|
67
|
+
(pTranspiler) => pTranspiler.name === "babel" && pTranspiler.available,
|
|
68
|
+
)
|
|
69
|
+
: true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {IOptions} pOptions
|
|
74
|
+
* @param {IAvailableTranspiler[]} pAvailableTranspilers
|
|
75
|
+
* @returns {{issues?: IEnvironmentIssue[]}}
|
|
76
|
+
*/
|
|
77
|
+
export function determineEnvironmentIssues(pOptions, pAvailableTranspilers) {
|
|
78
|
+
const lIssues = [];
|
|
79
|
+
if (!tscIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
80
|
+
const tsTranspiler = pAvailableTranspilers.find(
|
|
81
|
+
(pTranspiler) => pTranspiler.name === "typescript",
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
lIssues.push({
|
|
85
|
+
severity: "warn",
|
|
86
|
+
name: "missing-tsc-transpiler",
|
|
87
|
+
description:
|
|
88
|
+
`dependency-cruiser detected a TypeScript environment,${EOL}` +
|
|
89
|
+
` but not a compatible TypeScript compiler (${tsTranspiler.name}: ${tsTranspiler.version}). This means${EOL}` +
|
|
90
|
+
` it's likely to have missed TypeScript sources and dependencies.${EOL}${EOL}` +
|
|
91
|
+
` Install typescript to get better results (e.g. npm i -D typescript@^6 or typescript@^7.1).${EOL}${EOL}` +
|
|
92
|
+
` => typescript@7.0 is not supported as its API lacks what dependency-cruiser needs.${EOL}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!typeScriptIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
96
|
+
const tsTranspilersString = pAvailableTranspilers
|
|
97
|
+
.filter(
|
|
98
|
+
(pTranspiler) =>
|
|
99
|
+
pTranspiler.name === "typescript" || pTranspiler.name === "swc",
|
|
100
|
+
)
|
|
101
|
+
.map((pTranspiler) => `${pTranspiler.name}: ${pTranspiler.version}`)
|
|
102
|
+
.join(", ");
|
|
103
|
+
lIssues.push({
|
|
104
|
+
severity: "warn",
|
|
105
|
+
name: "missing-typescript-transpiler",
|
|
106
|
+
description:
|
|
107
|
+
`dependency-cruiser detected an environment that needs a TypeScript compiler of some sort,${EOL}` +
|
|
108
|
+
` but did not find one of the supported ones (${tsTranspilersString}).${EOL}` +
|
|
109
|
+
` This means it's likely to have missed TypeScript sources and dependencies.${EOL}${EOL}` +
|
|
110
|
+
` Install one of the above for better results.${EOL}${EOL}` +
|
|
111
|
+
` => if you choose to use swc don't forget to set the "parser" option to "swc"${EOL}` +
|
|
112
|
+
` => typescript@7.0 is not supported as its API lacks what dependency-cruiser needs.${EOL}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (!swcIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
116
|
+
const swcTranspiler = pAvailableTranspilers.find(
|
|
117
|
+
(pTranspiler) => pTranspiler.name === "swc",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
lIssues.push({
|
|
121
|
+
severity: "warn",
|
|
122
|
+
name: "missing-swc-transpiler",
|
|
123
|
+
description:
|
|
124
|
+
`dependency-cruiser found swc configured as compiler, but ${EOL}` +
|
|
125
|
+
` it did not find a compatible version of swc (${swcTranspiler.name}: ${swcTranspiler.version}).${EOL}` +
|
|
126
|
+
` dependency-cruiser might have missed some sources or dependencies.${EOL}${EOL}` +
|
|
127
|
+
` Install swc (e.g. npm i -D @swc/core) to get better results.${EOL}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (!babelIsConsistent(pOptions, pAvailableTranspilers)) {
|
|
131
|
+
const babelTranspiler = pAvailableTranspilers.find(
|
|
132
|
+
(pTranspiler) => pTranspiler.name === "babel",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
lIssues.push({
|
|
136
|
+
severity: "warn",
|
|
137
|
+
name: "missing-babel-transpiler",
|
|
138
|
+
description:
|
|
139
|
+
`dependency-cruiser detected a babel configuration, but not a${EOL}` +
|
|
140
|
+
` compatible babel transpiler (${babelTranspiler.name}: ${babelTranspiler.version}). If you indeed use babel,${EOL}` +
|
|
141
|
+
` dependency-cruiser might have missed some sources or dependencies.${EOL}${EOL}` +
|
|
142
|
+
` Install babel (e.g. npm i -D @babel/core@^7) to get better results.${EOL}`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return lIssues.length > 0 ? { issues: lIssues } : {};
|
|
146
|
+
}
|
|
@@ -112,7 +112,7 @@ module.exports = {
|
|
|
112
112
|
name: 'no-duplicate-dep-types',
|
|
113
113
|
comment:
|
|
114
114
|
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
|
115
|
-
"in your package.json i.e.
|
|
115
|
+
"in your package.json i.e. both as a devDependencies and in dependencies. This will cause " +
|
|
116
116
|
"maintenance problems later on.",
|
|
117
117
|
severity: 'warn',
|
|
118
118
|
from: {},
|
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import tryImport from "#utl/try-import.mjs";
|
|
3
|
-
import meta from "#meta.cjs";
|
|
4
|
-
|
|
5
|
-
const typescript = await tryImport(
|
|
6
|
-
"typescript",
|
|
7
|
-
meta.supportedTranspilers.typescript,
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
const FORMAT_DIAGNOSTICS_HOST = {
|
|
11
|
-
getCanonicalFileName(pFileName) {
|
|
12
|
-
let lReturnValue = pFileName.toLowerCase();
|
|
13
|
-
|
|
14
|
-
// depends on the platform which branch is taken, hence the c8 ignore
|
|
15
|
-
/* c8 ignore start */
|
|
16
|
-
if (typescript?.sys?.useCaseSensitiveFileNames ?? false) {
|
|
17
|
-
lReturnValue = pFileName;
|
|
18
|
-
}
|
|
19
|
-
/* c8 ignore stop */
|
|
20
|
-
return lReturnValue;
|
|
21
|
-
},
|
|
22
|
-
getCurrentDirectory() {
|
|
23
|
-
return process.cwd();
|
|
24
|
-
},
|
|
25
|
-
getNewLine() {
|
|
26
|
-
return "\n";
|
|
27
|
-
},
|
|
28
|
-
};
|
|
1
|
+
import typescript from "#utl/typescript-facade.mjs";
|
|
29
2
|
|
|
30
3
|
/**
|
|
31
4
|
* Reads the file with name `pTSConfigFileName` and returns its parsed
|
|
@@ -42,31 +15,18 @@ export default function extractTSConfig(pTSConfigFileName) {
|
|
|
42
15
|
let lReturnValue = {};
|
|
43
16
|
|
|
44
17
|
if (typescript) {
|
|
45
|
-
const lConfig = typescript.readConfigFile(
|
|
46
|
-
pTSConfigFileName,
|
|
47
|
-
typescript.sys.readFile,
|
|
48
|
-
);
|
|
18
|
+
const lConfig = typescript.readConfigFile(pTSConfigFileName);
|
|
49
19
|
|
|
50
20
|
if (typeof lConfig.error !== "undefined") {
|
|
51
|
-
throw new TypeError(
|
|
52
|
-
typescript.formatDiagnostics([lConfig.error], FORMAT_DIAGNOSTICS_HOST),
|
|
53
|
-
);
|
|
21
|
+
throw new TypeError(typescript.formatDiagnostics([lConfig.error]));
|
|
54
22
|
}
|
|
55
23
|
lReturnValue = typescript.parseJsonConfigFileContent(
|
|
56
24
|
lConfig.config,
|
|
57
|
-
typescript.sys,
|
|
58
|
-
dirname(resolve(pTSConfigFileName)),
|
|
59
|
-
{},
|
|
60
25
|
pTSConfigFileName,
|
|
61
26
|
);
|
|
62
27
|
|
|
63
28
|
if (lReturnValue.errors.length > 0) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
typescript.formatDiagnostics(
|
|
66
|
-
lReturnValue.errors,
|
|
67
|
-
FORMAT_DIAGNOSTICS_HOST,
|
|
68
|
-
),
|
|
69
|
-
);
|
|
29
|
+
throw new Error(typescript.formatDiagnostics(lReturnValue.errors));
|
|
70
30
|
}
|
|
71
31
|
}
|
|
72
32
|
|
|
@@ -22,7 +22,9 @@ function dependencyKeyHasModuleName(
|
|
|
22
22
|
pPrefix,
|
|
23
23
|
) {
|
|
24
24
|
return (pKey) =>
|
|
25
|
-
|
|
25
|
+
// dependencies, devDependencies, peerDependencies, optionalDependencies, bundleDependencies, bundledDependencies.
|
|
26
|
+
// But not peerDependenciesMeta. Or dependenciesIDoNotWantToTalkabout.
|
|
27
|
+
pKey.endsWith("ependencies") &&
|
|
26
28
|
Object.hasOwn(pPackageDependencies[pKey], join(pPrefix, pModuleName));
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -123,7 +123,7 @@ function nodeIsTypeOnly(pNode) {
|
|
|
123
123
|
return true;
|
|
124
124
|
}
|
|
125
125
|
return (
|
|
126
|
-
pNode?.specifiers
|
|
126
|
+
pNode?.specifiers?.length > 0 &&
|
|
127
127
|
pNode.specifiers.every((pSpecifier) => pSpecifier.isTypeOnly)
|
|
128
128
|
);
|
|
129
129
|
}
|
|
@@ -144,7 +144,9 @@ export default Visitor
|
|
|
144
144
|
module: pNode.source.value,
|
|
145
145
|
moduleSystem: "es6",
|
|
146
146
|
exoticallyRequired: false,
|
|
147
|
-
dependencyTypes:
|
|
147
|
+
dependencyTypes: nodeIsTypeOnly(pNode)
|
|
148
|
+
? ["export", "type-only"]
|
|
149
|
+
: ["export"],
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
152
|
}
|
|
@@ -55,10 +55,9 @@ const TRANSPILER2AVAILABLE = {
|
|
|
55
55
|
meta.supportedTranspilers.svelte,
|
|
56
56
|
),
|
|
57
57
|
swc: tryImportAvailable("@swc/core", meta.supportedTranspilers.swc),
|
|
58
|
-
typescript
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
),
|
|
58
|
+
// not tryImportAvailable as for typescript >=7 being installed isn't
|
|
59
|
+
// enough - the native compiler needs to be able to run as well
|
|
60
|
+
typescript: typescriptWrap.isAvailable(),
|
|
62
61
|
"vue-template-compiler": tryImportAvailable(
|
|
63
62
|
"vue-template-compiler",
|
|
64
63
|
meta.supportedTranspilers["vue-template-compiler"],
|
|
@@ -109,7 +108,19 @@ export const EXTENSION2AVAILABLE = new Map([
|
|
|
109
108
|
]);
|
|
110
109
|
|
|
111
110
|
const EXTENSIONS_PER_PARSER = {
|
|
112
|
-
swc: new Set([
|
|
111
|
+
swc: new Set([
|
|
112
|
+
".js",
|
|
113
|
+
".cjs",
|
|
114
|
+
".mjs",
|
|
115
|
+
".jsx",
|
|
116
|
+
".ts",
|
|
117
|
+
".tsx",
|
|
118
|
+
".d.ts",
|
|
119
|
+
".cts",
|
|
120
|
+
".d.cts",
|
|
121
|
+
".mts",
|
|
122
|
+
".d.mts",
|
|
123
|
+
]),
|
|
113
124
|
// tsc: [".js", ".cjs", ".mjs", ".jsx", ".ts", ".tsx", ".d.ts"],
|
|
114
125
|
// acorn: [".js", ".cjs", ".mjs", ".jsx"],
|
|
115
126
|
};
|
|
@@ -1,32 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import meta from "#meta.cjs";
|
|
3
|
-
|
|
4
|
-
const typescript = await tryImport(
|
|
5
|
-
"typescript",
|
|
6
|
-
meta.supportedTranspilers.typescript,
|
|
7
|
-
);
|
|
1
|
+
import typescript from "#utl/typescript-facade.mjs";
|
|
8
2
|
|
|
9
3
|
function getCompilerOptions(pFlavor, pTSConfig) {
|
|
10
4
|
const lCompilerOptions = {};
|
|
11
5
|
|
|
12
6
|
if (pFlavor === "tsx") {
|
|
13
|
-
lCompilerOptions.jsx =
|
|
7
|
+
lCompilerOptions.jsx = typescript.JsxEmit.React;
|
|
14
8
|
}
|
|
15
9
|
|
|
16
10
|
if (pFlavor === "esm") {
|
|
17
11
|
// see https://www.typescriptlang.org/docs/handbook/esm-node.html
|
|
18
|
-
lCompilerOptions.module =
|
|
12
|
+
lCompilerOptions.module = typescript.ModuleKind.NodeNext;
|
|
19
13
|
// when we're doing something with esm (so cts, mts extensions) we can
|
|
20
14
|
// assume TypeScript >=4.7 - which supports target es2022 - which should
|
|
21
15
|
// be easier (and hence we assume faster) to transpile
|
|
22
|
-
lCompilerOptions.target =
|
|
16
|
+
lCompilerOptions.target = typescript.ScriptTarget.ES2022;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
19
|
return {
|
|
26
20
|
// target: "es2022" should also be OK - but we don't know which version of
|
|
27
21
|
// the TypeScript compiler we're using, so for we keep this for backwards
|
|
28
22
|
// compatibility.
|
|
29
|
-
target:
|
|
23
|
+
target: typescript.ScriptTarget.ES2015,
|
|
30
24
|
...lCompilerOptions,
|
|
31
25
|
...(pTSConfig?.options ?? {}),
|
|
32
26
|
};
|
|
@@ -2,19 +2,12 @@
|
|
|
2
2
|
/* eslint-disable unicorn/prevent-abbreviations */
|
|
3
3
|
/* eslint-disable max-lines */
|
|
4
4
|
/* eslint-disable no-inline-comments */
|
|
5
|
-
import
|
|
6
|
-
import meta from "#meta.cjs";
|
|
5
|
+
import typescript from "#utl/typescript-facade.mjs";
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* @import
|
|
8
|
+
* @import {Node} from "typescript"
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
/** @type {typescript} */
|
|
13
|
-
const typescript = await tryImport(
|
|
14
|
-
"typescript",
|
|
15
|
-
meta.supportedTranspilers.typescript,
|
|
16
|
-
);
|
|
17
|
-
|
|
18
11
|
const INTERESTING_NODE_KINDS = typescript
|
|
19
12
|
? new Set([
|
|
20
13
|
typescript.SyntaxKind.CallExpression,
|
|
@@ -86,11 +79,17 @@ function getModuleBlock(pStatement) {
|
|
|
86
79
|
}
|
|
87
80
|
|
|
88
81
|
function getStatements(pAST, pDetectImportsInAmbientModules) {
|
|
82
|
+
// in javascript the native compiler adds statements it derived from jsdoc
|
|
83
|
+
// (e.g. from @import tags). Those are handled with the rest of the jsdoc.
|
|
84
|
+
const lStatements = (pAST.statements ?? []).filter(
|
|
85
|
+
(pStatement) => !typescript.isReparsed(pStatement),
|
|
86
|
+
);
|
|
87
|
+
|
|
89
88
|
if (!pDetectImportsInAmbientModules) {
|
|
90
|
-
return
|
|
89
|
+
return lStatements;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
return
|
|
92
|
+
return lStatements.flatMap((pStatement) => {
|
|
94
93
|
const lModuleBlock = getModuleBlock(pStatement);
|
|
95
94
|
|
|
96
95
|
return lModuleBlock
|
|
@@ -208,7 +207,9 @@ function extractTripleSlashDirectives(pAST) {
|
|
|
208
207
|
})),
|
|
209
208
|
)
|
|
210
209
|
.concat(
|
|
211
|
-
|
|
210
|
+
// the native compiler doesn't support amd anymore, so it doesn't
|
|
211
|
+
// deliver amdDependencies either
|
|
212
|
+
(pAST.amdDependencies ?? []).map((pReference) => ({
|
|
212
213
|
module: pReference.path,
|
|
213
214
|
moduleSystem: "tsd",
|
|
214
215
|
exoticallyRequired: false,
|
|
@@ -237,19 +238,11 @@ function isRequireCallExpression(pASTNode) {
|
|
|
237
238
|
pASTNode.kind === typescript.SyntaxKind.CallExpression &&
|
|
238
239
|
pASTNode.expression
|
|
239
240
|
) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
*/
|
|
246
|
-
const lSyntaxKind = typescript.identifierToKeywordKind
|
|
247
|
-
? typescript.SyntaxKind[
|
|
248
|
-
typescript.identifierToKeywordKind(pASTNode.expression)
|
|
249
|
-
]
|
|
250
|
-
: /* c8 ignore next 1 */
|
|
251
|
-
typescript.SyntaxKind[pASTNode.expression.originalKeywordKind];
|
|
252
|
-
return lSyntaxKind === "RequireKeyword" && firstArgumentIsAString(pASTNode);
|
|
241
|
+
return (
|
|
242
|
+
pASTNode.expression.kind === typescript.SyntaxKind.Identifier &&
|
|
243
|
+
pASTNode.expression.text === "require" &&
|
|
244
|
+
firstArgumentIsAString(pASTNode)
|
|
245
|
+
);
|
|
253
246
|
}
|
|
254
247
|
return false;
|
|
255
248
|
}
|
|
@@ -272,10 +265,10 @@ function isCompositeExoticRequire(pASTNode, pObjectName, pPropertyName) {
|
|
|
272
265
|
typescript.SyntaxKind.PropertyAccessExpression &&
|
|
273
266
|
pASTNode.expression.expression &&
|
|
274
267
|
pASTNode.expression.expression.kind === typescript.SyntaxKind.Identifier &&
|
|
275
|
-
pASTNode.expression.expression.
|
|
268
|
+
pASTNode.expression.expression.text === pObjectName &&
|
|
276
269
|
pASTNode.expression.name &&
|
|
277
270
|
pASTNode.expression.name.kind === typescript.SyntaxKind.Identifier &&
|
|
278
|
-
pASTNode.expression.name.
|
|
271
|
+
pASTNode.expression.name.text === pPropertyName &&
|
|
279
272
|
firstArgumentIsAString(pASTNode)
|
|
280
273
|
);
|
|
281
274
|
}
|
|
@@ -298,15 +291,15 @@ function isTripleCursedCompositeExoticRequire(
|
|
|
298
291
|
pASTNode.expression.expression.expression &&
|
|
299
292
|
pASTNode.expression.expression.expression.kind ===
|
|
300
293
|
typescript.SyntaxKind.Identifier &&
|
|
301
|
-
pASTNode.expression.expression.expression.
|
|
294
|
+
pASTNode.expression.expression.expression.text === pObjectName1 &&
|
|
302
295
|
// process
|
|
303
296
|
pASTNode.expression.expression.name.kind ===
|
|
304
297
|
typescript.SyntaxKind.Identifier &&
|
|
305
|
-
pASTNode.expression.expression.name.
|
|
298
|
+
pASTNode.expression.expression.name.text === pObjectName2 &&
|
|
306
299
|
// getBuiltinModule
|
|
307
300
|
pASTNode.expression.name &&
|
|
308
301
|
pASTNode.expression.name.kind === typescript.SyntaxKind.Identifier &&
|
|
309
|
-
pASTNode.expression.name.
|
|
302
|
+
pASTNode.expression.name.text === pPropertyName &&
|
|
310
303
|
firstArgumentIsAString(pASTNode)
|
|
311
304
|
);
|
|
312
305
|
}
|
|
@@ -344,7 +337,7 @@ function extractJSDocImportTags(pJSDocTags) {
|
|
|
344
337
|
return pJSDocTags
|
|
345
338
|
.filter(
|
|
346
339
|
(pTag) =>
|
|
347
|
-
pTag.tagName.
|
|
340
|
+
pTag.tagName.text === "import" &&
|
|
348
341
|
pTag.moduleSpecifier &&
|
|
349
342
|
pTag.moduleSpecifier.kind === typescript.SyntaxKind.StringLiteral &&
|
|
350
343
|
pTag.moduleSpecifier.text,
|
|
@@ -367,33 +360,13 @@ function isJSDocImport(pTypeNode) {
|
|
|
367
360
|
);
|
|
368
361
|
}
|
|
369
362
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
"transformFlags",
|
|
378
|
-
"id",
|
|
379
|
-
"flowNode",
|
|
380
|
-
"symbol",
|
|
381
|
-
"original",
|
|
382
|
-
]);
|
|
383
|
-
|
|
384
|
-
export function walkJSDoc(pObject, pCollection = new Set()) {
|
|
385
|
-
if (isJSDocImport(pObject)) {
|
|
386
|
-
pCollection.add(pObject.argument.literal.text);
|
|
387
|
-
} else if (Array.isArray(pObject)) {
|
|
388
|
-
for (const lValue of pObject) {
|
|
389
|
-
walkJSDoc(lValue, pCollection);
|
|
390
|
-
}
|
|
391
|
-
} else if (typeof pObject === "object") {
|
|
392
|
-
for (const lKey in pObject) {
|
|
393
|
-
if (!IGNORABLE_JSDOC_KEYS.has(lKey) && pObject[lKey]) {
|
|
394
|
-
walkJSDoc(pObject[lKey], pCollection);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
363
|
+
export function walkJSDoc(pNode, pCollection = new Set()) {
|
|
364
|
+
if (isJSDocImport(pNode)) {
|
|
365
|
+
pCollection.add(pNode.argument.literal.text);
|
|
366
|
+
} else {
|
|
367
|
+
typescript.forEachChild(pNode, (pChildNode) => {
|
|
368
|
+
walkJSDoc(pChildNode, pCollection);
|
|
369
|
+
});
|
|
397
370
|
}
|
|
398
371
|
}
|
|
399
372
|
|
|
@@ -408,7 +381,7 @@ function extractJSDocBracketImports(pJSDocTags) {
|
|
|
408
381
|
return pJSDocTags
|
|
409
382
|
.filter(
|
|
410
383
|
(pTag) =>
|
|
411
|
-
pTag.tagName.
|
|
384
|
+
pTag.tagName.text !== "import" &&
|
|
412
385
|
pTag.typeExpression?.kind === typescript.SyntaxKind.FirstJSDocNode,
|
|
413
386
|
)
|
|
414
387
|
.flatMap((pTag) => getJSDocImports(pTag))
|
|
@@ -534,6 +507,14 @@ function walk(
|
|
|
534
507
|
pDetectProcessBuiltinModuleCalls,
|
|
535
508
|
) {
|
|
536
509
|
return (pASTNode) => {
|
|
510
|
+
// in javascript the native compiler adds nodes it derived from jsdoc
|
|
511
|
+
// (e.g. for types in @type tags). Those are handled with the rest of the
|
|
512
|
+
// jsdoc. As only the native compiler has these, the c8 ignore.
|
|
513
|
+
/* c8 ignore start */
|
|
514
|
+
if (typescript.isReparsed(pASTNode)) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
/* c8 ignore stop */
|
|
537
518
|
// /** @import thing from './module' */ etc
|
|
538
519
|
// /** @type {import('module').thing}*/ etc
|
|
539
520
|
if (pDetectJSDocImports && pASTNode.jsDoc) {
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import transpile from "../transpile/index.mjs";
|
|
3
|
-
import tryImport from "#utl/try-import.mjs";
|
|
4
|
-
import meta from "#meta.cjs";
|
|
5
3
|
import getExtension from "#utl/get-extension.mjs";
|
|
4
|
+
import typescript from "#utl/typescript-facade.mjs";
|
|
6
5
|
|
|
7
|
-
/** @type {import('typescript')} */
|
|
8
|
-
const typescript = await tryImport(
|
|
9
|
-
"typescript",
|
|
10
|
-
meta.supportedTranspilers.typescript,
|
|
11
|
-
);
|
|
12
6
|
/** @type {Map<string, any>} */
|
|
13
7
|
const CACHE = new Map();
|
|
14
8
|
|
|
@@ -29,8 +23,6 @@ export function getASTFromSource(pFileRecord, pTranspileOptions) {
|
|
|
29
23
|
return typescript.createSourceFile(
|
|
30
24
|
pFileRecord.filename || "$internal-file-name",
|
|
31
25
|
lSource,
|
|
32
|
-
typescript.ScriptTarget.Latest,
|
|
33
|
-
false,
|
|
34
26
|
);
|
|
35
27
|
}
|
|
36
28
|
|
package/src/meta.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* generated - don't edit */
|
|
2
2
|
module.exports = {
|
|
3
|
-
version: "18.
|
|
3
|
+
version: "18.8.0-beta-1",
|
|
4
4
|
engines: {
|
|
5
5
|
node: "^22||^24||>=26",
|
|
6
6
|
},
|
|
@@ -11,7 +11,7 @@ module.exports = {
|
|
|
11
11
|
livescript: ">=1.0.0 <2.0.0",
|
|
12
12
|
svelte: ">=3.0.0 <6.0.0",
|
|
13
13
|
swc: ">=1.0.0 <2.0.0",
|
|
14
|
-
typescript: ">=2.0.0 <7.0.0",
|
|
14
|
+
typescript: ">=2.0.0 <7.0.0||>=7.1.0 <8.0.0",
|
|
15
15
|
"vue-template-compiler": ">=2.0.0 <3.0.0",
|
|
16
16
|
"@vue/compiler-sfc": ">=3.0.0 <4.0.0",
|
|
17
17
|
},
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/* eslint-disable security/detect-object-injection */
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import tryImport from "#utl/try-import.mjs";
|
|
4
|
+
import meta from "#meta.cjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One interface over the two flavors of the typescript compiler
|
|
8
|
+
* - typescript <7: the compiler written in typescript, with its API
|
|
9
|
+
* available in-process on the module itself.
|
|
10
|
+
* - typescript >=7.1: the native compiler. Its API (typescript/sync and
|
|
11
|
+
* typescript/ast) talks to a separate process running the compiler.
|
|
12
|
+
*
|
|
13
|
+
* @typedef {object} ITypeScriptFacade
|
|
14
|
+
* @property {boolean} isNative - true when this is the native (>=7.1) compiler
|
|
15
|
+
* @property {string} version - version of the typescript compiler
|
|
16
|
+
* @property {Record<string, any>} SyntaxKind - the compiler's SyntaxKind enum
|
|
17
|
+
* @property {Record<string, any>} ScriptTarget - the compiler's ScriptTarget enum
|
|
18
|
+
* @property {Record<string, any>} ModuleKind - the compiler's ModuleKind enum
|
|
19
|
+
* @property {Record<string, any>} JsxEmit - the compiler's JsxEmit enum
|
|
20
|
+
* @property {(pNode: any, pCallback: (pNode: any) => void) => void} forEachChild -
|
|
21
|
+
* calls pCallback for each child node of pNode
|
|
22
|
+
* @property {(pNode: any) => boolean} isReparsed - true for nodes the
|
|
23
|
+
* compiler synthesized from something else (e.g. jsdoc in javascript)
|
|
24
|
+
* @property {(pFileName: string, pSource: string) => any} createSourceFile -
|
|
25
|
+
* parses pSource into an AST
|
|
26
|
+
* @property {(pSource: string, pOptions?: {compilerOptions?: Record<string, any>, fileName?: string}) => {outputText: string}} transpileModule -
|
|
27
|
+
* transpiles pSource to javascript
|
|
28
|
+
* @property {(pFileName: string) => {config?: any, error?: any}} readConfigFile -
|
|
29
|
+
* reads the tsconfig pFileName
|
|
30
|
+
* @property {(pConfig: any, pFileName: string) => import("typescript").ParsedCommandLine} parseJsonConfigFileContent -
|
|
31
|
+
* parses (and flattens) the tsconfig pConfig that was read from pFileName
|
|
32
|
+
* @property {(pDiagnostics: readonly any[]) => string} formatDiagnostics -
|
|
33
|
+
* formats pDiagnostics into a human readable string
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const FIRST_NATIVE_MAJOR_VERSION = 7;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {typeof import("typescript")} pTypeScript
|
|
40
|
+
* @returns {ITypeScriptFacade}
|
|
41
|
+
*/
|
|
42
|
+
function getClassicFacade(pTypeScript) {
|
|
43
|
+
const lFormatDiagnosticsHost = {
|
|
44
|
+
getCanonicalFileName(pFileName) {
|
|
45
|
+
let lReturnValue = pFileName.toLowerCase();
|
|
46
|
+
|
|
47
|
+
// depends on the platform which branch is taken, hence the c8 ignore
|
|
48
|
+
/* c8 ignore start */
|
|
49
|
+
if (pTypeScript?.sys?.useCaseSensitiveFileNames ?? false) {
|
|
50
|
+
lReturnValue = pFileName;
|
|
51
|
+
}
|
|
52
|
+
/* c8 ignore stop */
|
|
53
|
+
return lReturnValue;
|
|
54
|
+
},
|
|
55
|
+
getCurrentDirectory() {
|
|
56
|
+
return process.cwd();
|
|
57
|
+
},
|
|
58
|
+
getNewLine() {
|
|
59
|
+
return "\n";
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
isNative: false,
|
|
65
|
+
version: pTypeScript.version,
|
|
66
|
+
SyntaxKind: pTypeScript.SyntaxKind,
|
|
67
|
+
ScriptTarget: pTypeScript.ScriptTarget,
|
|
68
|
+
ModuleKind: pTypeScript.ModuleKind,
|
|
69
|
+
JsxEmit: pTypeScript.JsxEmit,
|
|
70
|
+
forEachChild: (pNode, pCallback) => {
|
|
71
|
+
pTypeScript.forEachChild(pNode, pCallback);
|
|
72
|
+
},
|
|
73
|
+
isReparsed: () => false,
|
|
74
|
+
createSourceFile: (pFileName, pSource) =>
|
|
75
|
+
pTypeScript.createSourceFile(
|
|
76
|
+
pFileName,
|
|
77
|
+
pSource,
|
|
78
|
+
pTypeScript.ScriptTarget.Latest,
|
|
79
|
+
false,
|
|
80
|
+
),
|
|
81
|
+
transpileModule: (pSource, pOptions) =>
|
|
82
|
+
pTypeScript.transpileModule(pSource, pOptions),
|
|
83
|
+
readConfigFile: (pFileName) =>
|
|
84
|
+
pTypeScript.readConfigFile(pFileName, pTypeScript.sys.readFile),
|
|
85
|
+
parseJsonConfigFileContent: (pConfig, pFileName) =>
|
|
86
|
+
pTypeScript.parseJsonConfigFileContent(
|
|
87
|
+
pConfig,
|
|
88
|
+
pTypeScript.sys,
|
|
89
|
+
dirname(resolve(pFileName)),
|
|
90
|
+
{},
|
|
91
|
+
pFileName,
|
|
92
|
+
),
|
|
93
|
+
formatDiagnostics: (pDiagnostics) =>
|
|
94
|
+
pTypeScript.formatDiagnostics(pDiagnostics, lFormatDiagnosticsHost),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The code below only runs with the native compiler (or without any
|
|
99
|
+
// typescript compiler). The regular test (& coverage) run uses the classic
|
|
100
|
+
// one, so c8 can't see this is covered by the run against the native one.
|
|
101
|
+
/* c8 ignore start */
|
|
102
|
+
/**
|
|
103
|
+
* The classic compiler's transpileModule accepts both names ("commonjs") and
|
|
104
|
+
* values (1) for options that are enums. The native one only takes values,
|
|
105
|
+
* so this lets its own tsconfig parser translate any names into values.
|
|
106
|
+
* Options it doesn't recognize (anymore) are left out.
|
|
107
|
+
*
|
|
108
|
+
* @param {any} pAPI - instance of the native compiler's API
|
|
109
|
+
* @returns {(pCompilerOptions?: Record<string, any>) => Record<string, any>}
|
|
110
|
+
*/
|
|
111
|
+
function getCompilerOptionsNormalizer(pAPI) {
|
|
112
|
+
/** @type {Map<string, Record<string, any>>} */
|
|
113
|
+
const lCache = new Map();
|
|
114
|
+
|
|
115
|
+
return (pCompilerOptions = {}) => {
|
|
116
|
+
const lStringOptions = Object.fromEntries(
|
|
117
|
+
Object.entries(pCompilerOptions).filter(
|
|
118
|
+
([, pValue]) => typeof pValue === "string",
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
const lStringOptionKeys = Object.keys(lStringOptions);
|
|
122
|
+
|
|
123
|
+
if (lStringOptionKeys.length === 0) {
|
|
124
|
+
return pCompilerOptions;
|
|
125
|
+
}
|
|
126
|
+
const lCacheKey = JSON.stringify(lStringOptions);
|
|
127
|
+
|
|
128
|
+
if (!lCache.has(lCacheKey)) {
|
|
129
|
+
lCache.set(
|
|
130
|
+
lCacheKey,
|
|
131
|
+
pAPI.parseJsonConfigFileContent(
|
|
132
|
+
{ compilerOptions: lStringOptions, files: [] },
|
|
133
|
+
{ configDirectory: process.cwd() },
|
|
134
|
+
).options,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const lParsedOptions = lCache.get(lCacheKey);
|
|
138
|
+
|
|
139
|
+
return Object.fromEntries(
|
|
140
|
+
Object.entries(pCompilerOptions)
|
|
141
|
+
.map(([pKey, pValue]) =>
|
|
142
|
+
lStringOptionKeys.includes(pKey)
|
|
143
|
+
? [pKey, lParsedOptions[pKey]]
|
|
144
|
+
: [pKey, pValue],
|
|
145
|
+
)
|
|
146
|
+
.filter(([, pValue]) => pValue !== undefined),
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {{version: string}} pTypeScript
|
|
153
|
+
* @returns {Promise<ITypeScriptFacade>}
|
|
154
|
+
* @throws {Error} when the native compiler's binary isn't available (e.g.
|
|
155
|
+
* on unsupported platforms, or when optional dependencies weren't
|
|
156
|
+
* installed)
|
|
157
|
+
*/
|
|
158
|
+
async function getNativeFacade(pTypeScript) {
|
|
159
|
+
const [lSync, lAST] = await Promise.all([
|
|
160
|
+
import("typescript/sync"),
|
|
161
|
+
import("typescript/ast"),
|
|
162
|
+
]);
|
|
163
|
+
// this starts the compiler process. That process doesn't keep node from
|
|
164
|
+
// exiting and stops when node does, so there's no need to close it.
|
|
165
|
+
const lAPI = new lSync.API({ cwd: process.cwd() });
|
|
166
|
+
const normalizeCompilerOptions = getCompilerOptionsNormalizer(lAPI);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
isNative: true,
|
|
170
|
+
version: pTypeScript.version,
|
|
171
|
+
SyntaxKind: lAST.SyntaxKind,
|
|
172
|
+
ScriptTarget: lAST.ScriptTarget,
|
|
173
|
+
ModuleKind: lSync.ModuleKind,
|
|
174
|
+
JsxEmit: lSync.JsxEmit,
|
|
175
|
+
// nodes created by anything else than the native compiler (e.g. ASTs
|
|
176
|
+
// from json) don't have the forEachChild method. They're treated as
|
|
177
|
+
// having no children.
|
|
178
|
+
forEachChild: (pNode, pCallback) => {
|
|
179
|
+
pNode.forEachChild?.(pCallback);
|
|
180
|
+
},
|
|
181
|
+
isReparsed: (pNode) =>
|
|
182
|
+
// flags is a bit field
|
|
183
|
+
// oxlint-disable-next-line no-bitwise
|
|
184
|
+
((pNode.flags ?? 0) & lAST.NodeFlags.Reparsed) !== 0,
|
|
185
|
+
createSourceFile: (pFileName, pSource) => {
|
|
186
|
+
const lRetainedSourceFile = lAPI.createSourceFile(pFileName, pSource);
|
|
187
|
+
// the source file is already copied over from the compiler process,
|
|
188
|
+
// so it stays usable after releasing it there
|
|
189
|
+
lRetainedSourceFile.dispose();
|
|
190
|
+
return lRetainedSourceFile.sourceFile;
|
|
191
|
+
},
|
|
192
|
+
transpileModule: (pSource, pOptions) =>
|
|
193
|
+
lAPI.transpileModule(pSource, {
|
|
194
|
+
compilerOptions: normalizeCompilerOptions(pOptions?.compilerOptions),
|
|
195
|
+
fileName: pOptions?.fileName,
|
|
196
|
+
}),
|
|
197
|
+
// The compiler process resolves relative paths against the directory it
|
|
198
|
+
// was started in - which can differ from the current one => resolve
|
|
199
|
+
// here
|
|
200
|
+
readConfigFile: (pFileName) => lAPI.readConfigFile(resolve(pFileName)),
|
|
201
|
+
parseJsonConfigFileContent: (pConfig, pFileName) =>
|
|
202
|
+
lAPI.parseJsonConfigFileContent(pConfig, {
|
|
203
|
+
configFileName: resolve(pFileName),
|
|
204
|
+
}),
|
|
205
|
+
formatDiagnostics: (pDiagnostics) =>
|
|
206
|
+
lSync.formatDiagnostics(pDiagnostics, lAPI),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {any} pTypeScript - the typescript module, or false if it isn't available
|
|
212
|
+
* @returns {Promise<ITypeScriptFacade|false>}
|
|
213
|
+
*/
|
|
214
|
+
async function getFacade(pTypeScript) {
|
|
215
|
+
if (!pTypeScript) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
if (Number.parseInt(pTypeScript.version, 10) >= FIRST_NATIVE_MAJOR_VERSION) {
|
|
219
|
+
try {
|
|
220
|
+
return await getNativeFacade(pTypeScript);
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/* c8 ignore stop */
|
|
226
|
+
return getClassicFacade(pTypeScript);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The typescript compiler behind a facade that hides the differences
|
|
231
|
+
* between the classic (<7) and the native (>=7.1) compiler, or false when
|
|
232
|
+
* no supported version of the typescript compiler is available.
|
|
233
|
+
*
|
|
234
|
+
* @type {ITypeScriptFacade|false}
|
|
235
|
+
*/
|
|
236
|
+
export default await getFacade(
|
|
237
|
+
await tryImport("typescript", meta.supportedTranspilers.typescript),
|
|
238
|
+
);
|
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A tsconfig as parsed by the typescript compiler. Declared here as from
|
|
3
|
+
* typescript 7 the package's main entry point doesn't export its
|
|
4
|
+
* ParsedCommandLine type anymore.
|
|
5
|
+
*/
|
|
6
|
+
export interface IParsedTSConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The compiler options. Options with an enum type (target, module,
|
|
9
|
+
* moduleResolution, jsx, ...) have the enum's (numerical) value.
|
|
10
|
+
*/
|
|
11
|
+
options: Record<string, any>;
|
|
12
|
+
/**
|
|
13
|
+
* The (absolute) names of the files the tsconfig includes
|
|
14
|
+
*/
|
|
15
|
+
fileNames: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Errors the typescript compiler encountered parsing the tsconfig
|
|
18
|
+
*/
|
|
19
|
+
errors: any[];
|
|
20
|
+
/**
|
|
21
|
+
* The tsconfig as read from disk
|
|
22
|
+
*/
|
|
23
|
+
raw?: any;
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
2
26
|
|
|
3
27
|
/**
|
|
4
28
|
* Reads the file with name `pTSConfigFileName` and returns its parsed
|
|
@@ -13,4 +37,4 @@ import type { ParsedCommandLine } from "typescript";
|
|
|
13
37
|
*/
|
|
14
38
|
export default function extractTSConfig(
|
|
15
39
|
pTSConfigFileName: string,
|
|
16
|
-
):
|
|
40
|
+
): IParsedTSConfig;
|