@openrewrite/rewrite 8.96.3 → 8.96.5

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 (142) hide show
  1. package/dist/execution.d.ts +2 -0
  2. package/dist/execution.d.ts.map +1 -1
  3. package/dist/execution.js +4 -0
  4. package/dist/execution.js.map +1 -1
  5. package/dist/heavyweight.d.ts +8 -0
  6. package/dist/heavyweight.d.ts.map +1 -0
  7. package/dist/heavyweight.js +45 -0
  8. package/dist/heavyweight.js.map +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/java/tree.d.ts +11 -11
  14. package/dist/java/tree.d.ts.map +1 -1
  15. package/dist/java/visitor.d.ts.map +1 -1
  16. package/dist/java/visitor.js +46 -2
  17. package/dist/java/visitor.js.map +1 -1
  18. package/dist/javascript/add-import.d.ts +2 -1
  19. package/dist/javascript/add-import.d.ts.map +1 -1
  20. package/dist/javascript/add-import.js +14 -2
  21. package/dist/javascript/add-import.js.map +1 -1
  22. package/dist/javascript/amd.d.ts.map +1 -1
  23. package/dist/javascript/amd.js +2 -1
  24. package/dist/javascript/amd.js.map +1 -1
  25. package/dist/javascript/cleanup/add-parse-int-radix.js +1 -1
  26. package/dist/javascript/cleanup/add-parse-int-radix.js.map +1 -1
  27. package/dist/javascript/format/format.d.ts +12 -0
  28. package/dist/javascript/format/format.d.ts.map +1 -1
  29. package/dist/javascript/format/format.js +17 -3
  30. package/dist/javascript/format/format.js.map +1 -1
  31. package/dist/javascript/format/tabs-and-indents-visitor.d.ts.map +1 -1
  32. package/dist/javascript/format/tabs-and-indents-visitor.js +4 -0
  33. package/dist/javascript/format/tabs-and-indents-visitor.js.map +1 -1
  34. package/dist/javascript/format/whitespace-reconciler.d.ts.map +1 -1
  35. package/dist/javascript/format/whitespace-reconciler.js +4 -0
  36. package/dist/javascript/format/whitespace-reconciler.js.map +1 -1
  37. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  38. package/dist/javascript/node-resolution-result.js +2 -0
  39. package/dist/javascript/node-resolution-result.js.map +1 -1
  40. package/dist/javascript/parser-utils.d.ts.map +1 -1
  41. package/dist/javascript/parser-utils.js +16 -5
  42. package/dist/javascript/parser-utils.js.map +1 -1
  43. package/dist/javascript/templating/engine.d.ts.map +1 -1
  44. package/dist/javascript/templating/engine.js +11 -6
  45. package/dist/javascript/templating/engine.js.map +1 -1
  46. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  47. package/dist/javascript/templating/pattern.js +8 -0
  48. package/dist/javascript/templating/pattern.js.map +1 -1
  49. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  50. package/dist/javascript/templating/placeholder-replacement.js +15 -0
  51. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  52. package/dist/javascript/templating/precedence.js +1 -1
  53. package/dist/javascript/templating/precedence.js.map +1 -1
  54. package/dist/javascript/tree.d.ts +12 -12
  55. package/dist/javascript/tree.d.ts.map +1 -1
  56. package/dist/javascript/visitor.d.ts.map +1 -1
  57. package/dist/javascript/visitor.js +96 -0
  58. package/dist/javascript/visitor.js.map +1 -1
  59. package/dist/recipe.d.ts +5 -0
  60. package/dist/recipe.d.ts.map +1 -1
  61. package/dist/recipe.js +5 -0
  62. package/dist/recipe.js.map +1 -1
  63. package/dist/rewrite-javascript-version.txt +1 -1
  64. package/dist/run.d.ts +7 -1
  65. package/dist/run.d.ts.map +1 -1
  66. package/dist/run.js +40 -2
  67. package/dist/run.js.map +1 -1
  68. package/dist/test/accumulator-contents.d.ts +15 -0
  69. package/dist/test/accumulator-contents.d.ts.map +1 -0
  70. package/dist/test/accumulator-contents.js +185 -0
  71. package/dist/test/accumulator-contents.js.map +1 -0
  72. package/dist/test/empty-sources.d.ts +14 -0
  73. package/dist/test/empty-sources.d.ts.map +1 -0
  74. package/dist/test/empty-sources.js +98 -0
  75. package/dist/test/empty-sources.js.map +1 -0
  76. package/dist/test/formatting-scope.d.ts +18 -0
  77. package/dist/test/formatting-scope.d.ts.map +1 -0
  78. package/dist/test/formatting-scope.js +244 -0
  79. package/dist/test/formatting-scope.js.map +1 -0
  80. package/dist/test/probes.d.ts +25 -0
  81. package/dist/test/probes.d.ts.map +1 -0
  82. package/dist/test/probes.js +189 -0
  83. package/dist/test/probes.js.map +1 -0
  84. package/dist/test/rewrite-test.d.ts +27 -0
  85. package/dist/test/rewrite-test.d.ts.map +1 -1
  86. package/dist/test/rewrite-test.js +191 -21
  87. package/dist/test/rewrite-test.js.map +1 -1
  88. package/dist/test/tree-shapes.d.ts +15 -0
  89. package/dist/test/tree-shapes.d.ts.map +1 -0
  90. package/dist/test/tree-shapes.js +288 -0
  91. package/dist/test/tree-shapes.js.map +1 -0
  92. package/dist/test/unparsed-sources.d.ts +17 -0
  93. package/dist/test/unparsed-sources.d.ts.map +1 -0
  94. package/dist/test/unparsed-sources.js +113 -0
  95. package/dist/test/unparsed-sources.js.map +1 -0
  96. package/dist/tree.d.ts.map +1 -1
  97. package/dist/tree.js +2 -0
  98. package/dist/tree.js.map +1 -1
  99. package/dist/visitor.d.ts.map +1 -1
  100. package/dist/visitor.js +2 -0
  101. package/dist/visitor.js.map +1 -1
  102. package/node_modules/picomatch/LICENSE +21 -0
  103. package/node_modules/picomatch/README.md +743 -0
  104. package/node_modules/picomatch/index.js +17 -0
  105. package/node_modules/picomatch/lib/constants.js +184 -0
  106. package/node_modules/picomatch/lib/parse.js +1506 -0
  107. package/node_modules/picomatch/lib/picomatch.js +361 -0
  108. package/node_modules/picomatch/lib/scan.js +403 -0
  109. package/node_modules/picomatch/lib/utils.js +72 -0
  110. package/node_modules/picomatch/package.json +83 -0
  111. package/node_modules/picomatch/posix.js +3 -0
  112. package/package.json +88 -90
  113. package/src/execution.ts +6 -0
  114. package/src/heavyweight.ts +42 -0
  115. package/src/index.ts +1 -0
  116. package/src/java/tree.ts +11 -11
  117. package/src/java/visitor.ts +52 -2
  118. package/src/javascript/add-import.ts +11 -2
  119. package/src/javascript/amd.ts +4 -2
  120. package/src/javascript/cleanup/add-parse-int-radix.ts +2 -2
  121. package/src/javascript/format/format.ts +25 -1
  122. package/src/javascript/format/tabs-and-indents-visitor.ts +4 -0
  123. package/src/javascript/format/whitespace-reconciler.ts +4 -0
  124. package/src/javascript/node-resolution-result.ts +3 -0
  125. package/src/javascript/parser-utils.ts +19 -7
  126. package/src/javascript/templating/engine.ts +11 -6
  127. package/src/javascript/templating/pattern.ts +10 -0
  128. package/src/javascript/templating/placeholder-replacement.ts +16 -0
  129. package/src/javascript/templating/precedence.ts +1 -1
  130. package/src/javascript/tree.ts +12 -12
  131. package/src/javascript/visitor.ts +120 -0
  132. package/src/recipe.ts +5 -0
  133. package/src/run.ts +30 -2
  134. package/src/test/accumulator-contents.ts +194 -0
  135. package/src/test/empty-sources.ts +99 -0
  136. package/src/test/formatting-scope.ts +231 -0
  137. package/src/test/probes.ts +156 -0
  138. package/src/test/rewrite-test.ts +177 -24
  139. package/src/test/tree-shapes.ts +332 -0
  140. package/src/test/unparsed-sources.ts +138 -0
  141. package/src/tree.ts +3 -0
  142. package/src/visitor.ts +3 -0
package/package.json CHANGED
@@ -1,108 +1,106 @@
1
1
  {
2
- "name": "@openrewrite/rewrite",
3
- "version": "8.96.3",
4
- "license": "Moderne Source Available License",
5
- "description": "OpenRewrite JavaScript.",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/openrewrite/rewrite",
9
- "directory": "rewrite-javascript/rewrite"
2
+ "name" : "@openrewrite/rewrite",
3
+ "version" : "8.96.5",
4
+ "license" : "Moderne Source Available License",
5
+ "description" : "OpenRewrite JavaScript.",
6
+ "repository" : {
7
+ "type" : "git",
8
+ "url" : "https://github.com/openrewrite/rewrite",
9
+ "directory" : "rewrite-javascript/rewrite"
10
10
  },
11
- "homepage": "https://github.com/openrewrite/rewrite",
12
- "main": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
- "files": [
15
- "dist/**",
16
- "src/**"
17
- ],
18
- "allowScripts": {
19
- "bun": true,
20
- "@datadog/pprof": true
11
+ "homepage" : "https://github.com/openrewrite/rewrite",
12
+ "main" : "./dist/index.js",
13
+ "types" : "./dist/index.d.ts",
14
+ "files" : [ "dist/**", "src/**" ],
15
+ "allowScripts" : {
16
+ "bun" : true,
17
+ "@datadog/pprof" : true
21
18
  },
22
- "exports": {
23
- ".": {
24
- "types": "./dist/index.d.ts",
25
- "default": "./dist/index.js"
19
+ "exports" : {
20
+ "." : {
21
+ "types" : "./dist/index.d.ts",
22
+ "default" : "./dist/index.js"
26
23
  },
27
- "./text": {
28
- "types": "./dist/text/index.d.ts",
29
- "default": "./dist/text/index.js"
24
+ "./text" : {
25
+ "types" : "./dist/text/index.d.ts",
26
+ "default" : "./dist/text/index.js"
30
27
  },
31
- "./json": {
32
- "types": "./dist/json/index.d.ts",
33
- "default": "./dist/json/index.js"
28
+ "./json" : {
29
+ "types" : "./dist/json/index.d.ts",
30
+ "default" : "./dist/json/index.js"
34
31
  },
35
- "./java": {
36
- "types": "./dist/java/index.d.ts",
37
- "default": "./dist/java/index.js"
32
+ "./java" : {
33
+ "types" : "./dist/java/index.d.ts",
34
+ "default" : "./dist/java/index.js"
38
35
  },
39
- "./javascript": {
40
- "types": "./dist/javascript/index.d.ts",
41
- "default": "./dist/javascript/index.js"
36
+ "./javascript" : {
37
+ "types" : "./dist/javascript/index.d.ts",
38
+ "default" : "./dist/javascript/index.js"
42
39
  },
43
- "./rpc": {
44
- "types": "./dist/rpc/index.d.ts",
45
- "default": "./dist/rpc/index.js"
40
+ "./rpc" : {
41
+ "types" : "./dist/rpc/index.d.ts",
42
+ "default" : "./dist/rpc/index.js"
46
43
  },
47
- "./test": {
48
- "types": "./dist/test/index.d.ts",
49
- "default": "./dist/test/index.js"
44
+ "./test" : {
45
+ "types" : "./dist/test/index.d.ts",
46
+ "default" : "./dist/test/index.js"
50
47
  },
51
- "./test/java-rpc": {
52
- "types": "./dist/test/java-rpc.d.ts",
53
- "default": "./dist/test/java-rpc.js"
48
+ "./test/java-rpc" : {
49
+ "types" : "./dist/test/java-rpc.d.ts",
50
+ "default" : "./dist/test/java-rpc.js"
54
51
  },
55
- "./yaml": {
56
- "types": "./dist/yaml/index.d.ts",
57
- "default": "./dist/yaml/index.js"
52
+ "./yaml" : {
53
+ "types" : "./dist/yaml/index.d.ts",
54
+ "default" : "./dist/yaml/index.js"
58
55
  }
59
56
  },
60
- "scripts": {
61
- "prebuild": "bun -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun -e \"require('fs').rmSync('tsconfig.build.tsbuildinfo',{force:true})\"",
62
- "build": "tsc --build tsconfig.build.json",
63
- "postbuild": "bun -e \"require('fs').chmodSync('dist/rpc/server.js', 0o755); require('fs').chmodSync('dist/javascript/type-report-cli.js', 0o755)\" && npm link --no-audit --no-fund --loglevel=warn",
64
- "typecheck": "tsc --noEmit -p tsconfig.test.json",
65
- "dev": "tsc --watch -p tsconfig.json",
66
- "test": "npm run typecheck && npm run build && vitest run",
67
- "testhelper": "vitest run",
68
- "build:fixtures": "tsc --build fixtures/tsconfig.json",
69
- "start": "npm run build && node ./dist/rpc/server.js"
57
+ "scripts" : {
58
+ "prebuild" : "bun -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun -e \"require('fs').rmSync('tsconfig.build.tsbuildinfo',{force:true})\"",
59
+ "build" : "tsc --build tsconfig.build.json",
60
+ "postbuild" : "bun -e \"require('fs').chmodSync('dist/rpc/server.js', 0o755); require('fs').chmodSync('dist/javascript/type-report-cli.js', 0o755)\" && npm link --no-audit --no-fund --loglevel=warn",
61
+ "typecheck" : "tsc --noEmit -p tsconfig.test.json",
62
+ "dev" : "tsc --watch -p tsconfig.json",
63
+ "test" : "npm run typecheck && npm run build && vitest run",
64
+ "testhelper" : "vitest run",
65
+ "build:fixtures" : "tsc --build fixtures/tsconfig.json",
66
+ "start" : "npm run build && node ./dist/rpc/server.js"
70
67
  },
71
- "dependencies": {
72
- "@akashrajpurohit/snowflake-id": "^2.0.0",
73
- "@types/node": "^22.5.4",
74
- "commander": "^10.0.1",
75
- "diff": "^8.0.0",
76
- "jsonc-parser": "^3.3.1",
77
- "mutative": "^1.3.0",
78
- "picomatch": "^4.0.4",
79
- "semver": "^7.7.4",
80
- "tmp-promise": "^3.0.3",
81
- "typescript": "^6.0.3",
82
- "vscode-jsonrpc": "^8.2.1",
83
- "yaml": "^2.9.0"
68
+ "dependencies" : {
69
+ "@akashrajpurohit/snowflake-id" : "^2.0.0",
70
+ "@types/node" : "^22.5.4",
71
+ "commander" : "^10.0.1",
72
+ "diff" : "^8.0.0",
73
+ "jsonc-parser" : "^3.3.1",
74
+ "mutative" : "^1.3.0",
75
+ "picomatch" : "4.0.8-backpatch-00001",
76
+ "semver" : "^7.7.4",
77
+ "tmp-promise" : "^3.0.3",
78
+ "typescript" : "^6.0.3",
79
+ "vscode-jsonrpc" : "^8.2.1",
80
+ "yaml" : "^2.9.0"
84
81
  },
85
- "devDependencies": {
86
- "@types/benchmark": "^2.1.5",
87
- "@types/diff": "^8.0.0",
88
- "@types/picomatch": "^4.0.2",
89
- "@types/semver": "^7.7.1",
90
- "@types/tmp": "^0.2.6",
91
- "benchmark": "^2.1.4",
92
- "bun": "^1.3.5",
93
- "prettier": "^3.7.4",
94
- "vitest": "^4.1.8"
82
+ "devDependencies" : {
83
+ "@types/benchmark" : "^2.1.5",
84
+ "@types/diff" : "^8.0.0",
85
+ "@types/picomatch" : "^4.0.2",
86
+ "@types/semver" : "^7.7.1",
87
+ "@types/tmp" : "^0.2.6",
88
+ "benchmark" : "^2.1.4",
89
+ "bun" : "^1.3.5",
90
+ "prettier" : "^3.7.4",
91
+ "vitest" : "^4.1.8"
95
92
  },
96
- "optionalDependencies": {
97
- "@pyroscope/nodejs": "^0.4.0",
98
- "prettier": "^3.7.4"
93
+ "optionalDependencies" : {
94
+ "@pyroscope/nodejs" : "^0.4.0",
95
+ "prettier" : "^3.7.4"
99
96
  },
100
- "overrides": {
101
- "tmp": "^0.2.7",
102
- "lodash": "^4.18.1"
97
+ "overrides" : {
98
+ "tmp" : "^0.2.7",
99
+ "lodash" : "^4.18.1"
103
100
  },
104
- "bin": {
105
- "rewrite-rpc": "./dist/rpc/server.js",
106
- "rewrite-javascript-types": "./dist/javascript/type-report-cli.js"
107
- }
108
- }
101
+ "bin" : {
102
+ "rewrite-rpc" : "./dist/rpc/server.js",
103
+ "rewrite-javascript-types" : "./dist/javascript/type-report-cli.js"
104
+ },
105
+ "bundleDependencies" : [ "picomatch" ]
106
+ }
package/src/execution.ts CHANGED
@@ -14,11 +14,15 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {RpcCodec, RpcCodecs, RpcReceiveQueue, RpcSendQueue} from "./rpc";
17
+ import {registerHeavyweight} from "./heavyweight";
17
18
 
18
19
  export class ExecutionContext {
19
20
  /** Mirrors `org.openrewrite.ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT`. */
20
21
  static readonly REQUIRE_PRINT_EQUALS_INPUT = "org.openrewrite.requirePrintEqualsInput";
21
22
 
23
+ /** Mirrors `org.openrewrite.ExecutionContext.SCANNING_MUTATION_VALIDATION`, which the test harness sets. */
24
+ static readonly SCANNING_MUTATION_VALIDATION = "org.openrewrite.test.scanningMutationValidation";
25
+
22
26
  readonly kind: string = "org.openrewrite.InMemoryExecutionContext"
23
27
 
24
28
  constructor(public readonly messages: { [key: string | symbol]: any } = {}) {
@@ -45,6 +49,8 @@ export class ExecutionContext {
45
49
  }
46
50
  }
47
51
 
52
+ registerHeavyweight(ExecutionContext);
53
+
48
54
  const executionContextCodec: RpcCodec<ExecutionContext> = {
49
55
  rpcNew(): ExecutionContext {
50
56
  return new ExecutionContext();
@@ -0,0 +1,42 @@
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 {MarkersKind} from "./markers";
17
+
18
+ const heavyweightClasses: Function[] = [];
19
+ const heavyweightKinds = new Set<string>();
20
+
21
+ /**
22
+ * Registers a class, or the kind of a plain object such as a marker, as a type too expensive to keep in memory for a
23
+ * whole run, as in a ScanningRecipe's accumulator.
24
+ */
25
+ export function registerHeavyweight(type: string | (abstract new (...args: any[]) => unknown)): void {
26
+ if (typeof type === "string") {
27
+ heavyweightKinds.add(type);
28
+ } else if (!heavyweightClasses.includes(type)) {
29
+ heavyweightClasses.push(type);
30
+ }
31
+ }
32
+
33
+ /** The registered kind or class that `value` is of, or the kind of a tree, which is always heavyweight. */
34
+ export function heavyweightType(value: object): string | Function | undefined {
35
+ const kind = (value as { kind?: unknown }).kind;
36
+ // a tree has an id and markers, and so do the Markers that hold its markers
37
+ if (typeof kind === "string" && (heavyweightKinds.has(kind) ||
38
+ "id" in value && "markers" in value && kind !== MarkersKind.Markers)) {
39
+ return kind;
40
+ }
41
+ return heavyweightClasses.find(type => value instanceof type);
42
+ }
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export * from "./util";
33
33
  export * from "./recipe";
34
34
  export * from "./run";
35
35
  export * from "./marketplace";
36
+ export {registerHeavyweight} from "./heavyweight";
36
37
 
37
38
  // Install all recipes in this package
38
39
  export async function activate(marketplace: RecipeMarketplace): Promise<void> {
package/src/java/tree.ts CHANGED
@@ -267,7 +267,7 @@ export namespace J {
267
267
  }
268
268
  }
269
269
 
270
- export interface Block extends J {
270
+ export interface Block extends J, Statement {
271
271
  readonly kind: typeof Kind.Block;
272
272
  readonly static: RightPadded<boolean>;
273
273
  readonly statements: RightPadded<Statement>[];
@@ -426,7 +426,7 @@ export namespace J {
426
426
  }
427
427
  }
428
428
 
429
- export interface Import extends J {
429
+ export interface Import extends J, Statement {
430
430
  readonly kind: typeof Kind.Import;
431
431
  readonly static: LeftPadded<boolean>;
432
432
  readonly qualid: FieldAccess;
@@ -442,7 +442,7 @@ export namespace J {
442
442
  readonly modifier?: Modifier;
443
443
  }
444
444
 
445
- export interface DeconstructionPattern extends J, TypedTree {
445
+ export interface DeconstructionPattern extends J, Expression, TypedTree {
446
446
  readonly kind: typeof Kind.DeconstructionPattern;
447
447
  readonly deconstructor: Expression;
448
448
  readonly nested: Container<J>;
@@ -490,7 +490,7 @@ export namespace J {
490
490
  readonly codePoint: string;
491
491
  }
492
492
 
493
- export interface MemberReference extends J, MethodCall {
493
+ export interface MemberReference extends J, TypedTree, MethodCall {
494
494
  readonly kind: typeof Kind.MemberReference;
495
495
  readonly containing: RightPadded<Expression>;
496
496
  readonly typeParameters?: Container<Expression>;
@@ -499,7 +499,7 @@ export namespace J {
499
499
  readonly variableType?: Type.Variable;
500
500
  }
501
501
 
502
- export interface MethodDeclaration extends J, TypedTree {
502
+ export interface MethodDeclaration extends J, Statement, TypedTree {
503
503
  readonly kind: typeof Kind.MethodDeclaration;
504
504
  readonly leadingAnnotations: Annotation[];
505
505
  readonly modifiers: Modifier[];
@@ -515,7 +515,7 @@ export namespace J {
515
515
  readonly methodType?: Type.Method;
516
516
  }
517
517
 
518
- export interface MethodInvocation extends J, TypedTree, MethodCall {
518
+ export interface MethodInvocation extends J, Statement, TypedTree, MethodCall {
519
519
  readonly kind: typeof Kind.MethodInvocation;
520
520
  readonly select?: RightPadded<Expression>;
521
521
  readonly typeParameters?: Container<Expression>;
@@ -567,7 +567,7 @@ export namespace J {
567
567
  readonly type?: Type;
568
568
  }
569
569
 
570
- export interface NewClass extends J, TypedTree, MethodCall {
570
+ export interface NewClass extends J, Statement, TypedTree, MethodCall {
571
571
  readonly kind: typeof Kind.NewClass;
572
572
  readonly enclosing?: RightPadded<Expression>;
573
573
  readonly new: Space;
@@ -583,13 +583,13 @@ export namespace J {
583
583
  readonly typeTree: RightPadded<TypeTree>;
584
584
  }
585
585
 
586
- export interface Package extends J {
586
+ export interface Package extends J, Statement {
587
587
  readonly kind: typeof Kind.Package;
588
588
  readonly expression: Expression;
589
589
  readonly annotations?: Annotation[];
590
590
  }
591
591
 
592
- export interface ParameterizedType extends J, TypeTree {
592
+ export interface ParameterizedType extends J, TypeTree, Expression {
593
593
  readonly kind: typeof Kind.ParameterizedType;
594
594
  readonly class: NameTree;
595
595
  readonly typeParameters?: Container<Expression>;
@@ -711,7 +711,7 @@ export namespace J {
711
711
  }
712
712
  }
713
713
 
714
- export interface VariableDeclarations extends J, TypedTree {
714
+ export interface VariableDeclarations extends J, Statement, TypedTree {
715
715
  readonly kind: typeof Kind.VariableDeclarations;
716
716
  readonly leadingAnnotations: Annotation[];
717
717
  readonly modifiers: Modifier[];
@@ -721,7 +721,7 @@ export namespace J {
721
721
  }
722
722
 
723
723
  export namespace VariableDeclarations {
724
- export interface NamedVariable extends J {
724
+ export interface NamedVariable extends J, NameTree {
725
725
  readonly kind: typeof Kind.NamedVariable;
726
726
  readonly name: VariableDeclarator;
727
727
  readonly dimensionsAfterName: LeftPadded<Space>[];
@@ -199,6 +199,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
199
199
  }
200
200
  assignOp = expression as J.AssignmentOperation;
201
201
 
202
+ const statement = await this.visitStatement(assignOp, p);
203
+ if (!statement?.kind || statement.kind !== J.Kind.AssignmentOperation) {
204
+ return statement;
205
+ }
206
+ assignOp = statement as J.AssignmentOperation;
207
+
202
208
  const updates = {
203
209
  prefix: await this.visitSpace(assignOp.prefix, p),
204
210
  markers: await this.visitMarkers(assignOp.markers, p),
@@ -215,6 +221,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
215
221
  if (!expression?.kind || expression.kind !== J.Kind.Binary) {
216
222
  return expression;
217
223
  }
224
+ const given = this.cursor;
225
+ if (expression !== binary && given.value === binary) {
226
+ // the operands are visited under the binary that holds them, where a template reads their slots
227
+ this.cursor = new Cursor(expression, given.parent);
228
+ given.messages.forEach((value, key) => this.cursor.messages.set(key, value));
229
+ }
218
230
  binary = expression as J.Binary;
219
231
 
220
232
  const updates = {
@@ -225,10 +237,21 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
225
237
  right: await this.visitDefined(binary.right, p) as Expression,
226
238
  type: await this.visitType(binary.type, p)
227
239
  };
240
+ if (this.cursor !== given) {
241
+ // what follows reads the cursor of the binary it was given, as for every other tree
242
+ this.cursor.messages.forEach((value, key) => given.messages.set(key, value));
243
+ this.cursor = given;
244
+ }
228
245
  return updateIfChanged(binary, updates);
229
246
  }
230
247
 
231
248
  protected async visitBlock(block: J.Block, p: P): Promise<J | undefined> {
249
+ const statement = await this.visitStatement(block, p);
250
+ if (!statement?.kind || statement.kind !== J.Kind.Block) {
251
+ return statement;
252
+ }
253
+ block = statement as J.Block;
254
+
232
255
  const updates = {
233
256
  prefix: await this.visitSpace(block.prefix, p),
234
257
  markers: await this.visitMarkers(block.markers, p),
@@ -340,6 +363,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
340
363
  }
341
364
 
342
365
  protected async visitControlParentheses<T extends J>(controlParens: J.ControlParentheses<T>, p: P): Promise<J | undefined> {
366
+ const expression = await this.visitExpression(controlParens, p);
367
+ if (!expression?.kind || expression.kind !== J.Kind.ControlParentheses) {
368
+ return expression;
369
+ }
370
+ controlParens = expression as J.ControlParentheses<T>;
371
+
343
372
  const updates: any = {
344
373
  prefix: await this.visitSpace(controlParens.prefix, p),
345
374
  markers: await this.visitMarkers(controlParens.markers, p),
@@ -797,6 +826,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
797
826
  }
798
827
  newClass = expression as J.NewClass;
799
828
 
829
+ const statement = await this.visitStatement(newClass, p);
830
+ if (!statement?.kind || statement.kind !== J.Kind.NewClass) {
831
+ return statement;
832
+ }
833
+ newClass = statement as J.NewClass;
834
+
800
835
  const updates: any = {
801
836
  prefix: await this.visitSpace(newClass.prefix, p),
802
837
  markers: await this.visitMarkers(newClass.markers, p),
@@ -845,6 +880,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
845
880
  }
846
881
 
847
882
  protected async visitParameterizedType(parameterizedType: J.ParameterizedType, p: P): Promise<J | undefined> {
883
+ const expression = await this.visitExpression(parameterizedType, p);
884
+ if (!expression?.kind || expression.kind !== J.Kind.ParameterizedType) {
885
+ return expression;
886
+ }
887
+ parameterizedType = expression as J.ParameterizedType;
888
+
848
889
  const updates: any = {
849
890
  prefix: await this.visitSpace(parameterizedType.prefix, p),
850
891
  markers: await this.visitMarkers(parameterizedType.markers, p),
@@ -856,6 +897,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
856
897
  }
857
898
 
858
899
  protected async visitParentheses<T extends J>(parentheses: J.Parentheses<T>, p: P): Promise<J | undefined> {
900
+ const expression = await this.visitExpression(parentheses, p);
901
+ if (!expression?.kind || expression.kind !== J.Kind.Parentheses) {
902
+ return expression;
903
+ }
904
+ parentheses = expression as J.Parentheses<T>;
905
+
859
906
  const updates: any = {
860
907
  prefix: await this.visitSpace(parentheses.prefix, p),
861
908
  markers: await this.visitMarkers(parentheses.markers, p),
@@ -1205,9 +1252,12 @@ export class JavaVisitor<P> extends TreeVisitor<J, P> {
1205
1252
 
1206
1253
  const updates: any = {
1207
1254
  prefix: await this.visitSpace(aYield.prefix, p),
1208
- markers: await this.visitMarkers(aYield.markers, p),
1209
- value: await this.visitDefined(aYield.value, p) as Expression
1255
+ markers: await this.visitMarkers(aYield.markers, p)
1210
1256
  };
1257
+ // a bare `yield` has no value
1258
+ if (aYield.value) {
1259
+ updates.value = await this.visitDefined(aYield.value, p) as Expression;
1260
+ }
1211
1261
  return updateIfChanged(aYield, updates);
1212
1262
  }
1213
1263
 
@@ -12,6 +12,7 @@ import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, i
12
12
  import {create as produce, Draft} from "mutative";
13
13
  import {autoFormat} from "./format";
14
14
  import {getPrettierStyle} from "./format/prettier-format";
15
+ import ts from "typescript";
15
16
 
16
17
  export type QuoteChar = "'" | '"';
17
18
 
@@ -562,7 +563,8 @@ function claimedName(v: unknown): string | undefined {
562
563
  /**
563
564
  * The parser lifts surrogate pairs out of `valueSource` into `unicodeEscapes` and, when it does,
564
565
  * sets `value` to the quoted source (`parser.ts` `mapLiteral`), so neither field alone is the
565
- * module name. Reuniting them and stripping the quotes yields it for either shape of literal.
566
+ * module name. Reuniting them and reading the quoted text as the language does yields it for
567
+ * either shape of literal.
566
568
  */
567
569
  export function moduleNameOf(module: string | J.Literal): string {
568
570
  if (typeof module === 'string') {
@@ -579,6 +581,13 @@ export function moduleNameOf(module: string | J.Literal): string {
579
581
  cut = escape.valueSourceIndex;
580
582
  }
581
583
  restored += source.slice(cut);
584
+ // an escape such as `\u00dc` names the character it spells
585
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, restored);
586
+ const token = scanner.scan();
587
+ if ((token === ts.SyntaxKind.StringLiteral || token === ts.SyntaxKind.NoSubstitutionTemplateLiteral) &&
588
+ scanner.getTokenEnd() === restored.length) {
589
+ return scanner.getTokenValue();
590
+ }
582
591
  const quote = restored.charAt(0);
583
592
  return (quote === "'" || quote === '"' || quote === '`') && restored.endsWith(quote) && restored.length > 1
584
593
  ? restored.slice(1, -1)
@@ -1583,7 +1592,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
1583
1592
  value: this.module,
1584
1593
  valueSource,
1585
1594
  unicodeEscapes: this.moduleUnicodeEscapes,
1586
- type: undefined
1595
+ type: Type.Primitive.String
1587
1596
  };
1588
1597
  }
1589
1598
 
@@ -25,7 +25,8 @@ import {
25
25
  rightPadded,
26
26
  space,
27
27
  Statement,
28
- TrailingComma
28
+ TrailingComma,
29
+ Type
29
30
  } from "../java";
30
31
  import {JS} from "./tree";
31
32
  import {moduleNameOf} from "./add-import";
@@ -428,7 +429,8 @@ function dependencyLiteral(module: string, quote: string): J.Literal {
428
429
  prefix: emptySpace,
429
430
  markers: emptyMarkers,
430
431
  value: module,
431
- valueSource: `${quote}${module}${quote}`
432
+ valueSource: `${quote}${module}${quote}`,
433
+ type: Type.Primitive.String
432
434
  };
433
435
  }
434
436
 
@@ -18,7 +18,7 @@ import {Recipe} from "../../recipe";
18
18
  import {TreeVisitor} from "../../visitor";
19
19
  import {ExecutionContext} from "../../execution";
20
20
  import {JavaScriptVisitor} from "../visitor";
21
- import {J, singleSpace} from "../../java";
21
+ import {J, singleSpace, Type} from "../../java";
22
22
  import {emptyMarkers} from "../../markers";
23
23
  import {randomId} from "../../uuid";
24
24
 
@@ -73,7 +73,7 @@ export class AddParseIntRadix extends Recipe {
73
73
  value: 10,
74
74
  valueSource: '10',
75
75
  unicodeEscapes: undefined,
76
- type: undefined
76
+ type: Type.Primitive.Double
77
77
  };
78
78
 
79
79
  return {
@@ -26,6 +26,7 @@ import {TabsAndIndentsVisitor} from "./tabs-and-indents-visitor";
26
26
  import {NormalizeWhitespaceVisitor} from "./normalize-whitespace-visitor";
27
27
  import {MinimumViableSpacingVisitor} from "./minimum-viable-spacing-visitor";
28
28
  import {applyPrettierFormatting, getPrettierStyle} from "./prettier-format";
29
+ import {AsyncLocalStorage} from "node:async_hooks";
29
30
 
30
31
  export {TabsAndIndentsVisitor} from "./tabs-and-indents-visitor";
31
32
  export {NormalizeWhitespaceVisitor} from "./normalize-whitespace-visitor";
@@ -56,6 +57,22 @@ export const autoIndent = async <J2 extends J, P>(j: J2, p: P, parent?: Cursor):
56
57
  return await new TabsAndIndentsVisitor(style).visit(j, p, parent) as J2;
57
58
  }
58
59
 
60
+ /**
61
+ * Hears about each request to auto-format a tree made while it listens, so that a test harness can check that a
62
+ * recipe formats only what it changed.
63
+ */
64
+ export interface FormattingListener {
65
+ /** AutoformatVisitor calls this before it formats `tree`, stopping after `stopAfter` when it is given. */
66
+ onFormat(tree: Tree, stopAfter: Tree | undefined, cursor: Cursor | undefined): void;
67
+ }
68
+
69
+ const formattingListener = new AsyncLocalStorage<FormattingListener>();
70
+
71
+ /** Runs `run`, telling `listener` about the formatting it requests. */
72
+ export function listenToFormatting<T>(listener: FormattingListener, run: () => Promise<T>): Promise<T> {
73
+ return formattingListener.run(listener, run);
74
+ }
75
+
59
76
  /**
60
77
  * Formats JavaScript/TypeScript code using a comprehensive set of formatting rules.
61
78
  *
@@ -77,6 +94,7 @@ export class AutoformatVisitor<P> extends JavaScriptVisitor<P> {
77
94
  }
78
95
 
79
96
  async visit<R extends J>(tree: Tree, p: P, cursor?: Cursor): Promise<R | undefined> {
97
+ formattingListener.getStore()?.onFormat(tree, this.stopAfter, cursor);
80
98
  const prettierStyle = getPrettierStyle(tree, cursor, this.styles);
81
99
  if (prettierStyle) {
82
100
  const formatted = await applyPrettierFormatting(tree as R, prettierStyle, p, cursor, this.stopAfter);
@@ -1121,7 +1139,7 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1121
1139
  } else if (parent?.kind === J.Kind.Block && grandparent?.kind !== J.Kind.NewClass && grandparent?.kind !== JS.Kind.TypeLiteral ||
1122
1140
  (parent?.kind === JS.Kind.CompilationUnit && (parent! as JS.CompilationUnit).statements[0].element.id != draft.id) ||
1123
1141
  (parent?.kind === J.Kind.Case && this.isInsideCaseStatements(parent as J.Case, draft))) {
1124
- if (draft.kind != J.Kind.Case) {
1142
+ if (draft.kind != J.Kind.Case && !this.opensCaseBody(parent, draft)) {
1125
1143
  this.ensurePrefixHasNewLine(draft);
1126
1144
  }
1127
1145
  }
@@ -1151,6 +1169,12 @@ export class BlankLinesVisitor<P> extends JavaScriptVisitor<P> {
1151
1169
  * Check if a statement is inside a Case's statements container or body,
1152
1170
  * not inside the caseLabels container.
1153
1171
  */
1172
+ /** A block that opens a case's statements stays on the line of its label, as in `case X: {`. */
1173
+ private opensCaseBody(parent: J | undefined, statement: J): boolean {
1174
+ return parent?.kind === J.Kind.Case && statement.kind === J.Kind.Block &&
1175
+ (parent as J.Case).statements.elements[0]?.element.id === statement.id;
1176
+ }
1177
+
1154
1178
  private isInsideCaseStatements(caseNode: J.Case, statement: J): boolean {
1155
1179
  // Check if statement is in the statements container
1156
1180
  if (caseNode.statements.elements.some(s => s.element.id === statement.id)) {
@@ -408,6 +408,10 @@ export class TabsAndIndentsVisitor<P> extends JavaScriptVisitor<P> {
408
408
  return literal;
409
409
  }
410
410
  return produce(literal, draft => {
411
+ // JSX text is its own value, as the parser makes it
412
+ if (draft.value === draft.valueSource) {
413
+ draft.value = normalizedValueSource;
414
+ }
411
415
  draft.valueSource = normalizedValueSource;
412
416
  });
413
417
  }
@@ -416,6 +416,10 @@ export class WhitespaceReconciler {
416
416
  original: J.Identifier | J.Literal,
417
417
  formatted: J.Identifier | J.Literal
418
418
  ): J.Identifier | J.Literal {
419
+ // A literal's type follows from how it is written, so an identifier's is not the literal's
420
+ if (isLiteral(formatted)) {
421
+ return formatted;
422
+ }
419
423
  const result: Record<string, unknown> = { ...formatted };
420
424
 
421
425
  // Preserve type attribution - both Identifier and Literal have `type`