@openrewrite/rewrite 8.91.2 → 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.
Files changed (130) hide show
  1. package/dist/execution.d.ts +8 -0
  2. package/dist/execution.d.ts.map +1 -1
  3. package/dist/execution.js +31 -0
  4. package/dist/execution.js.map +1 -1
  5. package/dist/javascript/add-import.d.ts +64 -9
  6. package/dist/javascript/add-import.d.ts.map +1 -1
  7. package/dist/javascript/add-import.js +398 -71
  8. package/dist/javascript/add-import.js.map +1 -1
  9. package/dist/javascript/amd.d.ts +11 -1
  10. package/dist/javascript/amd.d.ts.map +1 -1
  11. package/dist/javascript/amd.js +43 -9
  12. package/dist/javascript/amd.js.map +1 -1
  13. package/dist/javascript/binding.d.ts +10 -2
  14. package/dist/javascript/binding.d.ts.map +1 -1
  15. package/dist/javascript/binding.js +44 -71
  16. package/dist/javascript/binding.js.map +1 -1
  17. package/dist/javascript/comparator.d.ts +2 -0
  18. package/dist/javascript/comparator.d.ts.map +1 -1
  19. package/dist/javascript/comparator.js +5 -1
  20. package/dist/javascript/comparator.js.map +1 -1
  21. package/dist/javascript/format/format.d.ts +5 -2
  22. package/dist/javascript/format/format.d.ts.map +1 -1
  23. package/dist/javascript/format/format.js +51 -11
  24. package/dist/javascript/format/format.js.map +1 -1
  25. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts +19 -0
  26. package/dist/javascript/format/minimum-viable-spacing-visitor.d.ts.map +1 -1
  27. package/dist/javascript/format/minimum-viable-spacing-visitor.js +180 -7
  28. package/dist/javascript/format/minimum-viable-spacing-visitor.js.map +1 -1
  29. package/dist/javascript/format/prettier-format.d.ts +2 -1
  30. package/dist/javascript/format/prettier-format.d.ts.map +1 -1
  31. package/dist/javascript/format/prettier-format.js +33 -23
  32. package/dist/javascript/format/prettier-format.js.map +1 -1
  33. package/dist/javascript/index.d.ts +3 -3
  34. package/dist/javascript/index.d.ts.map +1 -1
  35. package/dist/javascript/index.js +21 -1
  36. package/dist/javascript/index.js.map +1 -1
  37. package/dist/javascript/parser-utils.d.ts.map +1 -1
  38. package/dist/javascript/parser-utils.js +5 -9
  39. package/dist/javascript/parser-utils.js.map +1 -1
  40. package/dist/javascript/parser.d.ts +3 -2
  41. package/dist/javascript/parser.d.ts.map +1 -1
  42. package/dist/javascript/parser.js +202 -117
  43. package/dist/javascript/parser.js.map +1 -1
  44. package/dist/javascript/print.d.ts +1 -0
  45. package/dist/javascript/print.d.ts.map +1 -1
  46. package/dist/javascript/print.js +9 -0
  47. package/dist/javascript/print.js.map +1 -1
  48. package/dist/javascript/recipes/change-import.d.ts +6 -3
  49. package/dist/javascript/recipes/change-import.d.ts.map +1 -1
  50. package/dist/javascript/recipes/change-import.js +17 -277
  51. package/dist/javascript/recipes/change-import.js.map +1 -1
  52. package/dist/javascript/rpc.js +2 -0
  53. package/dist/javascript/rpc.js.map +1 -1
  54. package/dist/javascript/scope.d.ts +40 -7
  55. package/dist/javascript/scope.d.ts.map +1 -1
  56. package/dist/javascript/scope.js +182 -30
  57. package/dist/javascript/scope.js.map +1 -1
  58. package/dist/javascript/templating/bindings.js +2 -20
  59. package/dist/javascript/templating/bindings.js.map +1 -1
  60. package/dist/javascript/templating/comparator.d.ts +2 -0
  61. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  62. package/dist/javascript/templating/comparator.js +25 -0
  63. package/dist/javascript/templating/comparator.js.map +1 -1
  64. package/dist/javascript/templating/engine.d.ts +5 -0
  65. package/dist/javascript/templating/engine.d.ts.map +1 -1
  66. package/dist/javascript/templating/engine.js +30 -12
  67. package/dist/javascript/templating/engine.js.map +1 -1
  68. package/dist/javascript/templating/template.d.ts.map +1 -1
  69. package/dist/javascript/templating/template.js +6 -0
  70. package/dist/javascript/templating/template.js.map +1 -1
  71. package/dist/javascript/templating/types.d.ts +5 -0
  72. package/dist/javascript/templating/types.d.ts.map +1 -1
  73. package/dist/javascript/tree.d.ts +1 -0
  74. package/dist/javascript/tree.d.ts.map +1 -1
  75. package/dist/javascript/tree.js.map +1 -1
  76. package/dist/javascript/tsconfig.d.ts +32 -0
  77. package/dist/javascript/tsconfig.d.ts.map +1 -0
  78. package/dist/javascript/tsconfig.js +205 -0
  79. package/dist/javascript/tsconfig.js.map +1 -0
  80. package/dist/javascript/type-mapping.d.ts.map +1 -1
  81. package/dist/javascript/type-mapping.js +14 -0
  82. package/dist/javascript/type-mapping.js.map +1 -1
  83. package/dist/javascript/visitor.d.ts.map +1 -1
  84. package/dist/javascript/visitor.js +1 -0
  85. package/dist/javascript/visitor.js.map +1 -1
  86. package/dist/parser.d.ts +6 -0
  87. package/dist/parser.d.ts.map +1 -1
  88. package/dist/parser.js +23 -1
  89. package/dist/parser.js.map +1 -1
  90. package/dist/print.d.ts.map +1 -1
  91. package/dist/print.js +5 -2
  92. package/dist/print.js.map +1 -1
  93. package/dist/rewrite-javascript-version.txt +1 -1
  94. package/dist/rpc/request/parse-project.d.ts +11 -1
  95. package/dist/rpc/request/parse-project.d.ts.map +1 -1
  96. package/dist/rpc/request/parse-project.js +7 -2
  97. package/dist/rpc/request/parse-project.js.map +1 -1
  98. package/dist/rpc/request/parse.d.ts +13 -1
  99. package/dist/rpc/request/parse.d.ts.map +1 -1
  100. package/dist/rpc/request/parse.js +8 -2
  101. package/dist/rpc/request/parse.js.map +1 -1
  102. package/package.json +1 -1
  103. package/src/execution.ts +23 -0
  104. package/src/javascript/add-import.ts +449 -78
  105. package/src/javascript/amd.ts +49 -10
  106. package/src/javascript/binding.ts +60 -74
  107. package/src/javascript/comparator.ts +6 -1
  108. package/src/javascript/format/format.ts +56 -12
  109. package/src/javascript/format/minimum-viable-spacing-visitor.ts +196 -7
  110. package/src/javascript/format/prettier-format.ts +35 -25
  111. package/src/javascript/index.ts +10 -3
  112. package/src/javascript/parser-utils.ts +5 -11
  113. package/src/javascript/parser.ts +167 -62
  114. package/src/javascript/print.ts +10 -0
  115. package/src/javascript/recipes/change-import.ts +19 -349
  116. package/src/javascript/rpc.ts +2 -0
  117. package/src/javascript/scope.ts +210 -31
  118. package/src/javascript/templating/bindings.ts +2 -25
  119. package/src/javascript/templating/comparator.ts +29 -2
  120. package/src/javascript/templating/engine.ts +41 -14
  121. package/src/javascript/templating/template.ts +6 -0
  122. package/src/javascript/templating/types.ts +6 -0
  123. package/src/javascript/tree.ts +1 -0
  124. package/src/javascript/tsconfig.ts +177 -0
  125. package/src/javascript/type-mapping.ts +15 -0
  126. package/src/javascript/visitor.ts +1 -0
  127. package/src/parser.ts +24 -1
  128. package/src/print.ts +5 -2
  129. package/src/rpc/request/parse-project.ts +6 -2
  130. package/src/rpc/request/parse.ts +7 -2
@@ -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
+ }
@@ -19,6 +19,20 @@ import {Type} from "../java";
19
19
  import FUNCTION_TYPE_NAME = Type.FUNCTION_TYPE_NAME;
20
20
  import OBJECT_TYPE_NAME = Type.OBJECT_TYPE_NAME;
21
21
 
22
+ /**
23
+ * TypeScript orders union constituents by internal type id, which varies with what the checker resolved
24
+ * earlier in the run; ordering by signature keeps a union's signature the same across parses. Intersections
25
+ * keep the order they were written in and are left alone. Call once `bounds` is attached to its owner, so
26
+ * a constituent that reaches back into it reads the whole constituent list rather than an empty one.
27
+ */
28
+ function sortBySignature(bounds: Type[]): void {
29
+ const keys = new Map<Type, string>(bounds.map(b => [b, Type.signature(b)]));
30
+ bounds.sort((a, b) => {
31
+ const ka = keys.get(a)!, kb = keys.get(b)!;
32
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
33
+ });
34
+ }
35
+
22
36
  export class JavaScriptTypeMapping {
23
37
  // Primary cache: Use type signatures (preferring type.id) as cache keys
24
38
  // TypeScript assigns stable IDs to all types, so we don't need secondary caches
@@ -1610,6 +1624,7 @@ export class JavaScriptTypeMapping {
1610
1624
 
1611
1625
  // Update the bounds in the union we created
1612
1626
  (union as any).bounds = bounds;
1627
+ sortBySignature(bounds);
1613
1628
 
1614
1629
  return union;
1615
1630
  }
@@ -551,6 +551,7 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
551
551
  const updates: any = {
552
552
  prefix: await this.visitSpace(propertyAssignment.prefix, p),
553
553
  markers: await this.visitMarkers(propertyAssignment.markers, p),
554
+ modifiers: await mapAsync(propertyAssignment.modifiers, m => this.visitDefined<J.Modifier>(m, p)),
554
555
  name: await this.visitRightPadded(propertyAssignment.name, p),
555
556
  initializer: propertyAssignment.initializer && await this.visitDefined<Expression>(propertyAssignment.initializer, p)
556
557
  };
package/src/parser.ts CHANGED
@@ -13,8 +13,10 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import {createTwoFilesPatch} from "diff";
16
17
  import {ExecutionContext} from "./execution";
17
18
  import {SourceFile} from "./tree";
19
+ import {TreePrinters} from "./print";
18
20
  import fs, {readFileSync} from "node:fs";
19
21
  import {isAbsolute, relative} from "path";
20
22
  import {ParseError, ParseErrorKind} from "./parse-error";
@@ -79,6 +81,27 @@ export abstract class Parser {
79
81
  return isAbsolute(path) && this.relativeTo ? relative(this.relativeTo, path) : path;
80
82
  }
81
83
 
84
+ /**
85
+ * A parse result that does not print back to its input has silently lost source, so it is reported as
86
+ * a `ParseError` carrying the tree that lost it. Mirrors `org.openrewrite.Parser`, down to the message.
87
+ * A parser whose LST cannot hold some byte of its input passes the form it can hold as `source`.
88
+ */
89
+ protected async requirePrintEqualsInput(sourceFile: SourceFile, input: ParserInput,
90
+ source: string = parserInputRead(input)): Promise<SourceFile> {
91
+ if (!this.ctx.getBoolean(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, true)) {
92
+ return sourceFile;
93
+ }
94
+ const printed = await TreePrinters.print(sourceFile);
95
+ if (printed === source) {
96
+ return sourceFile;
97
+ }
98
+ const path = this.relativePath(input);
99
+ const diff = createTwoFilesPatch(path, path, source, printed, "", "", {context: 3});
100
+ const error = this.error(input, new Error(`${sourceFile.sourcePath} is not print idempotent. \n${diff}`));
101
+ const withErroneous: ParseError = {...error, erroneous: sourceFile};
102
+ return withErroneous;
103
+ }
104
+
82
105
  protected error(input: ParserInput, e: Error): ParseError {
83
106
  return {
84
107
  kind: ParseErrorKind,
@@ -88,7 +111,7 @@ export abstract class Parser {
88
111
  id: randomId(),
89
112
  parserType: this.constructor.name,
90
113
  exceptionType: e.name,
91
- message: e.message + ':\n' + e.stack,
114
+ message: e.stack ?? e.message,
92
115
  } satisfies ParseExceptionResult as ParseExceptionResult),
93
116
  text: parserInputRead(input),
94
117
  sourcePath: this.relativePath(input),
package/src/print.ts CHANGED
@@ -162,8 +162,11 @@ export class TreePrinters {
162
162
  return this._registry.get(sourceFileKind)!;
163
163
  }
164
164
 
165
- static print(sourceFile: SourceFile): Promise<string> {
166
- return this.printer(sourceFile).print(sourceFile);
165
+ static async print(sourceFile: SourceFile): Promise<string> {
166
+ const printed = await this.printer(sourceFile).print(sourceFile);
167
+ // The BOM is a property of the file's encoding rather than of its syntax, so it is held on the
168
+ // source file and put back here. Mirrors `org.openrewrite.Tree.print`.
169
+ return sourceFile.charsetBomMarked && printed.charAt(0) !== "\uFEFF" ? "\uFEFF" + printed : printed;
167
170
  }
168
171
 
169
172
  static async printTrimmed(sourceFile: SourceFile): Promise<string> {
@@ -55,7 +55,11 @@ export class ParseProject {
55
55
  * If not specified, paths are relative to projectPath.
56
56
  * Use this when parsing a subdirectory but wanting paths relative to the repository root.
57
57
  */
58
- private readonly relativeTo?: string
58
+ private readonly relativeTo?: string,
59
+ /**
60
+ * Parser options, as on the `Parse` request.
61
+ */
62
+ private readonly options?: { [key: string]: string }
59
63
  ) {}
60
64
 
61
65
  static handle(
@@ -85,7 +89,7 @@ export class ParseProject {
85
89
  const prettierLoader = await projectParser.createPrettierLoader();
86
90
 
87
91
  const resultItems: ParseProjectResponseItem[] = [];
88
- const ctx = new ExecutionContext();
92
+ const ctx = new ExecutionContext({...request.options});
89
93
 
90
94
  // Parse package.json files (these get NodeResolutionResult markers)
91
95
  if (discovered.packageJsonFiles.length > 0) {
@@ -23,7 +23,12 @@ import * as path from "path";
23
23
 
24
24
  export class Parse {
25
25
  constructor(private readonly inputs: ParserInput[],
26
- private readonly relativeTo?: string) {
26
+ private readonly relativeTo?: string,
27
+ /**
28
+ * Parser options from the peer. Keys this handler does not recognize are ignored, and a
29
+ * peer that sends none gets the parser's own defaults.
30
+ */
31
+ private readonly options?: { [key: string]: string }) {
27
32
  }
28
33
 
29
34
  /**
@@ -58,7 +63,7 @@ export class Parse {
58
63
  : "javascript";
59
64
 
60
65
  const parser = Parsers.createParser(parserType, {
61
- ctx: new ExecutionContext(),
66
+ ctx: new ExecutionContext({...request.options}),
62
67
  relativeTo: request.relativeTo
63
68
  })!;
64
69