@openrewrite/rewrite 8.91.3 → 8.91.4
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/dist/javascript/parser.d.ts +3 -2
- package/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +154 -114
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts +32 -0
- package/dist/javascript/tsconfig.d.ts.map +1 -0
- package/dist/javascript/tsconfig.js +205 -0
- package/dist/javascript/tsconfig.js.map +1 -0
- package/dist/rewrite-javascript-version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/parser.ts +104 -58
- package/src/javascript/tsconfig.ts +177 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 the original author or authors.
|
|
3
|
+
* <p>
|
|
4
|
+
* Licensed under the Moderne Source Available License (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
* <p>
|
|
8
|
+
* https://docs.moderne.io/licensing/moderne-source-available-license
|
|
9
|
+
* <p>
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import ts from "typescript";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* What a project gets to decide: where a module specifier resolves to, and which ambient
|
|
21
|
+
* declarations are in scope. The shape of the types themselves stays with the parser — `target`
|
|
22
|
+
* and `lib` bound the global scope, `strict` adds a `null` to every nullable type — which pins
|
|
23
|
+
* the widest view it can rather than the one a project compiles against.
|
|
24
|
+
*/
|
|
25
|
+
const RESOLUTION_OPTIONS: string[] = [
|
|
26
|
+
"baseUrl",
|
|
27
|
+
"paths",
|
|
28
|
+
// Internal companion to `paths`: the directory its entries resolve against.
|
|
29
|
+
"pathsBasePath",
|
|
30
|
+
"rootDirs",
|
|
31
|
+
"types",
|
|
32
|
+
"typeRoots",
|
|
33
|
+
"moduleResolution",
|
|
34
|
+
"moduleSuffixes",
|
|
35
|
+
"customConditions",
|
|
36
|
+
"resolvePackageJsonExports",
|
|
37
|
+
"resolvePackageJsonImports",
|
|
38
|
+
"resolveJsonModule",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Config file names in precedence order. TypeScript reads jsconfig.json for JavaScript
|
|
43
|
+
* projects, which is where a JS-only codebase states its `paths`.
|
|
44
|
+
*/
|
|
45
|
+
const CONFIG_FILE_NAMES = ["tsconfig.json", "jsconfig.json"];
|
|
46
|
+
|
|
47
|
+
export interface ResolvedTsConfig {
|
|
48
|
+
/**
|
|
49
|
+
* Absolute path of the config file the options came from. Undefined when no project
|
|
50
|
+
* config applied, in which case {@link options} are the parser's defaults.
|
|
51
|
+
*/
|
|
52
|
+
readonly configFilePath?: string;
|
|
53
|
+
readonly options: ts.CompilerOptions;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finds the config file governing each file and merges its resolution options over a set of
|
|
58
|
+
* parser defaults, so that type-aware recipes see the same modules `tsc` would.
|
|
59
|
+
*/
|
|
60
|
+
export class TsConfigResolver {
|
|
61
|
+
private readonly configByDir = new Map<string, string | undefined>();
|
|
62
|
+
private readonly optionsByConfig = new Map<string, ts.CompilerOptions | undefined>();
|
|
63
|
+
private readonly root?: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param defaults Options to fall back on, and to merge a project config over.
|
|
67
|
+
* @param relativeTo Root of the project being parsed, bounding the search. Without one,
|
|
68
|
+
* walking up from the process working directory picks up whatever config sits above it.
|
|
69
|
+
*/
|
|
70
|
+
constructor(private readonly defaults: ts.CompilerOptions, relativeTo?: string) {
|
|
71
|
+
this.root = relativeTo ? path.resolve(relativeTo) : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
forFile(filePath: string): ResolvedTsConfig {
|
|
75
|
+
const configFilePath = this.findConfig(path.dirname(path.resolve(filePath)));
|
|
76
|
+
const options = configFilePath === undefined ? undefined : this.optionsFor(configFilePath);
|
|
77
|
+
return options === undefined ? {options: this.defaults} : {configFilePath, options};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The nearest config file at or below the project root, searching upwards. */
|
|
81
|
+
private findConfig(from: string): string | undefined {
|
|
82
|
+
if (!this.root || !isAtOrUnder(from, this.root)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const searched: string[] = [];
|
|
87
|
+
const memoize = (found: string | undefined) => {
|
|
88
|
+
for (const dir of searched) {
|
|
89
|
+
this.configByDir.set(dir, found);
|
|
90
|
+
}
|
|
91
|
+
return found;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
for (let dir = from; ; dir = path.dirname(dir)) {
|
|
95
|
+
if (this.configByDir.has(dir)) {
|
|
96
|
+
return memoize(this.configByDir.get(dir));
|
|
97
|
+
}
|
|
98
|
+
searched.push(dir);
|
|
99
|
+
|
|
100
|
+
for (const configFileName of CONFIG_FILE_NAMES) {
|
|
101
|
+
const candidate = path.join(dir, configFileName);
|
|
102
|
+
if (ts.sys.fileExists(candidate)) {
|
|
103
|
+
return memoize(candidate);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (dir === this.root || path.dirname(dir) === dir) {
|
|
107
|
+
return memoize(undefined);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private optionsFor(configFilePath: string): ts.CompilerOptions | undefined {
|
|
113
|
+
if (!this.optionsByConfig.has(configFilePath)) {
|
|
114
|
+
this.optionsByConfig.set(configFilePath, this.merge(configFilePath));
|
|
115
|
+
}
|
|
116
|
+
return this.optionsByConfig.get(configFilePath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Undefined where the config cannot be read, leaving the caller on its defaults. */
|
|
120
|
+
private merge(configFilePath: string): ts.CompilerOptions | undefined {
|
|
121
|
+
let projectOptions: ts.CompilerOptions;
|
|
122
|
+
try {
|
|
123
|
+
const {config, error} = ts.readConfigFile(configFilePath, ts.sys.readFile);
|
|
124
|
+
if (error || !config) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
// `readDirectory` answers empty because only `options` is read here; left to `ts.sys` it
|
|
128
|
+
// walks the whole tree under the config to build a file list, once per config found.
|
|
129
|
+
const parseConfigHost: ts.ParseConfigHost = {...ts.sys, readDirectory: () => []};
|
|
130
|
+
projectOptions = ts.parseJsonConfigFileContent(
|
|
131
|
+
config, parseConfigHost, path.dirname(configFilePath), undefined, configFilePath).options;
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A project's `baseUrl` stands alone, including where it states none: `baseUrl` takes
|
|
137
|
+
// precedence over `pathsBasePath`, so a default rooted at the repository would resolve a
|
|
138
|
+
// nested project's `paths` against the wrong directory.
|
|
139
|
+
const {baseUrl: _, ...defaults} = this.defaults;
|
|
140
|
+
|
|
141
|
+
const resolution: ts.CompilerOptions = {};
|
|
142
|
+
for (const option of RESOLUTION_OPTIONS) {
|
|
143
|
+
if (projectOptions[option] !== undefined) {
|
|
144
|
+
resolution[option] = projectOptions[option];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (resolution.moduleResolution === undefined) {
|
|
148
|
+
const paired = pairedModuleResolution(projectOptions);
|
|
149
|
+
if (paired !== undefined) {
|
|
150
|
+
resolution.moduleResolution = paired;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {...defaults, ...resolution};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The resolution TypeScript infers for a project that states only its `module`. Only the
|
|
159
|
+
* inference is adopted, since a module kind such as `node16` classifies files as CJS or ESM,
|
|
160
|
+
* and an ESM-only import from a CJS file is then the error TS1479, costing the file its LST.
|
|
161
|
+
* `Classic` is declined as well, searching no `node_modules`, so the default stands in.
|
|
162
|
+
*/
|
|
163
|
+
function pairedModuleResolution(projectOptions: ts.CompilerOptions): ts.ModuleResolutionKind | undefined {
|
|
164
|
+
const infer = (ts as Partial<{ getEmitModuleResolutionKind(options: ts.CompilerOptions): ts.ModuleResolutionKind }>)
|
|
165
|
+
.getEmitModuleResolutionKind;
|
|
166
|
+
if (projectOptions.module === undefined || !infer) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
const inferred = infer(projectOptions);
|
|
170
|
+
return inferred === ts.ModuleResolutionKind.Classic ? undefined : inferred;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isAtOrUnder(dir: string, root: string): boolean {
|
|
174
|
+
const relative = path.relative(root, dir);
|
|
175
|
+
return relative === "" ||
|
|
176
|
+
(relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative));
|
|
177
|
+
}
|