@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
@@ -19,6 +19,7 @@ import {asRef} from "../reference";
19
19
  import {RpcCodecs, RpcReceiveQueue, RpcSendQueue} from "../rpc";
20
20
  import {castDraft} from "mutative";
21
21
  import {updateIfChanged} from "../util";
22
+ import {registerHeavyweight} from "../heavyweight";
22
23
  import * as semver from "semver";
23
24
  import * as fsp from "fs/promises";
24
25
  import * as path from "path";
@@ -259,6 +260,8 @@ export interface NodeResolutionResult extends Marker {
259
260
  readonly npmrcConfigs?: Npmrc[];
260
261
  }
261
262
 
263
+ registerHeavyweight(NodeResolutionResultKind);
264
+
262
265
  /**
263
266
  * Creates a NodeResolutionResult marker from a package.json file.
264
267
  * Should be called during parsing to attach to JS.CompilationUnit.
@@ -17,7 +17,8 @@ import * as ts from "typescript";
17
17
  import {Expression, J, Statement} from "../java";
18
18
  import {JS} from "./tree";
19
19
 
20
- const is_statements: string[] = [
20
+ // Built on first use, since ./tree imports the visitor, which imports this module
21
+ const statementKinds = (): string[] => [
21
22
  J.Kind.Assert,
22
23
  J.Kind.Assignment,
23
24
  J.Kind.AssignmentOperation,
@@ -55,7 +56,6 @@ const is_statements: string[] = [
55
56
  JS.Kind.ArrowFunction,
56
57
  JS.Kind.BindingElement,
57
58
  JS.Kind.Delete,
58
- JS.Kind.Export,
59
59
  JS.Kind.ExportAssignment,
60
60
  JS.Kind.ExportDeclaration,
61
61
  JS.Kind.ForInLoop,
@@ -70,6 +70,8 @@ const is_statements: string[] = [
70
70
  JS.Kind.NamespaceDeclaration,
71
71
  JS.Kind.PropertyAssignment,
72
72
  JS.Kind.ScopedVariableDeclarations,
73
+ JS.Kind.Shebang,
74
+ JS.Kind.Spread,
73
75
  JS.Kind.TaggedTemplateExpression,
74
76
  JS.Kind.TemplateExpression,
75
77
  JS.Kind.TypeDeclaration,
@@ -77,9 +79,9 @@ const is_statements: string[] = [
77
79
  JS.Kind.WithStatement,
78
80
  JS.Kind.ExpressionStatement,
79
81
  JS.Kind.StatementExpression
80
- ]
82
+ ];
81
83
 
82
- const is_expressions: string[] = [
84
+ const expressionKinds = (): string[] => [
83
85
  J.Kind.AnnotatedType,
84
86
  J.Kind.Annotation,
85
87
  J.Kind.ArrayAccess,
@@ -88,6 +90,7 @@ const is_expressions: string[] = [
88
90
  J.Kind.AssignmentOperation,
89
91
  J.Kind.Binary,
90
92
  J.Kind.ControlParentheses,
93
+ J.Kind.DeconstructionPattern,
91
94
  J.Kind.Empty,
92
95
  J.Kind.Erroneous,
93
96
  J.Kind.FieldAccess,
@@ -114,18 +117,23 @@ const is_expressions: string[] = [
114
117
  JS.Kind.Alias,
115
118
  JS.Kind.ArrayBindingPattern,
116
119
  JS.Kind.ArrowFunction,
120
+ JS.Kind.As,
117
121
  JS.Kind.Await,
118
122
  JS.Kind.BindingElement,
123
+ JS.Kind.ComputedPropertyName,
119
124
  JS.Kind.ConditionalType,
120
125
  JS.Kind.Delete,
121
126
  JS.Kind.ExportSpecifier,
122
127
  JS.Kind.ExpressionWithTypeArguments,
128
+ JS.Kind.FunctionCall,
123
129
  JS.Kind.FunctionType,
124
130
  JS.Kind.ImportType,
125
131
  JS.Kind.IndexedAccessType,
126
132
  JS.Kind.IndexedAccessTypeIndexType,
127
133
  JS.Kind.InferType,
128
134
  JS.Kind.Intersection,
135
+ JS.Kind.JsxEmbeddedExpression,
136
+ JS.Kind.JsxTag,
129
137
  JS.Kind.AssignmentOperation,
130
138
  JS.Kind.Binary,
131
139
  JS.Kind.ImportSpecifier,
@@ -135,6 +143,7 @@ const is_expressions: string[] = [
135
143
  JS.Kind.NamedImports,
136
144
  JS.Kind.ObjectBindingPattern,
137
145
  JS.Kind.SatisfiesExpression,
146
+ JS.Kind.Spread,
138
147
  JS.Kind.TaggedTemplateExpression,
139
148
  JS.Kind.TemplateExpression,
140
149
  JS.Kind.Tuple,
@@ -149,14 +158,17 @@ const is_expressions: string[] = [
149
158
  JS.Kind.Void,
150
159
  JS.Kind.ExpressionStatement,
151
160
  JS.Kind.StatementExpression
152
- ]
161
+ ];
162
+
163
+ let is_statements: Set<string> | undefined;
164
+ let is_expressions: Set<string> | undefined;
153
165
 
154
166
  export function isStatement(statement: J): statement is Statement {
155
- return is_statements.includes(statement.kind);
167
+ return (is_statements ??= new Set(statementKinds())).has(statement.kind);
156
168
  }
157
169
 
158
170
  export function isExpression(expression: J): expression is Expression {
159
- return is_expressions.includes(expression.kind);
171
+ return (is_expressions ??= new Set(expressionKinds())).has(expression.kind);
160
172
  }
161
173
 
162
174
  export function getNextSibling(node: ts.Node): ts.Node | undefined {
@@ -756,14 +756,18 @@ class MarkerAttachmentVisitor extends JavaScriptVisitor<undefined> {
756
756
  }
757
757
 
758
758
  // FIXME: This is a heuristic to determine if the parent expects a statement child
759
- function expectsStatement(parentTree: J): boolean {
759
+ function expectsStatement(parentTree: J, childId: string): boolean {
760
+ if (parentTree.kind === J.Kind.Case) {
761
+ // the labels of a case are expressions
762
+ return !(parentTree as J.Case).caseLabels.elements.some(label => label.element.id === childId);
763
+ }
760
764
  return parentTree.kind === J.Kind.Block ||
761
- parentTree.kind === J.Kind.Case ||
762
765
  parentTree.kind === J.Kind.DoWhileLoop ||
763
766
  parentTree.kind === J.Kind.ForEachLoop ||
764
767
  parentTree.kind === J.Kind.ForLoop ||
765
768
  parentTree.kind === J.Kind.If ||
766
769
  parentTree.kind === J.Kind.IfElse ||
770
+ parentTree.kind === J.Kind.Label ||
767
771
  parentTree.kind === J.Kind.WhileLoop ||
768
772
  parentTree.kind === JS.Kind.CompilationUnit ||
769
773
  parentTree.kind === JS.Kind.ForInLoop;
@@ -782,7 +786,7 @@ export function replacedByObjectLiteral(tree: J, cursor: Cursor): boolean {
782
786
  return true;
783
787
  }
784
788
  const parentTree = cursor.parentTree()?.value;
785
- return parentTree !== undefined && !expectsStatement(parentTree);
789
+ return parentTree !== undefined && !expectsStatement(parentTree, tree.id);
786
790
  }
787
791
 
788
792
  /**
@@ -915,7 +919,7 @@ export class TemplateApplier {
915
919
 
916
920
  // Only apply wrapping logic if we have parent context
917
921
  if (parentTree) {
918
- const parentExpectsStatement = expectsStatement(parentTree);
922
+ const parentExpectsStatement = expectsStatement(parentTree, originalTree.id);
919
923
  const originalIsStatement = isStatement(originalTree);
920
924
 
921
925
  const resultIsStatement = isStatement(resultToUse);
@@ -935,8 +939,9 @@ export class TemplateApplier {
935
939
  expression: { ...expression, prefix: emptySpace }
936
940
  } as JS.ExpressionStatement;
937
941
  }
938
- } else if (!parentExpectsStatement) {
939
- // Expression context: wrap in StatementExpression if result is statement-only
942
+ } else if (!parentExpectsStatement && isExpression(originalTree)) {
943
+ // Expression context: wrap in StatementExpression if result is statement-only. A slot that held
944
+ // a statement-only node, such as an arrow function's block body, takes one unwrapped.
940
945
  if (resultIsStatement && !resultIsExpression) {
941
946
  const stmt = resultToUse as Statement;
942
947
  resultToUse = {
@@ -756,6 +756,16 @@ class Matcher {
756
756
  opensWithBrace(this.pattern.templateParts) && this.ast.kind !== J.Kind.Block);
757
757
  }
758
758
 
759
+ // A visit reaches what parentheses hold right after them, so seeing through them here would match it twice
760
+ if ((this.ast.kind === J.Kind.Parentheses || this.ast.kind === J.Kind.ControlParentheses) &&
761
+ this.patternAst.kind !== this.ast.kind) {
762
+ if (this.debugOptions.enabled) {
763
+ this.setExplanation('kind-mismatch', 'An expression without parentheses',
764
+ `Parentheses (${this.ast.kind.split('.').pop()})`,
765
+ 'The expression the parentheses hold is matched on its own');
766
+ }
767
+ return false;
768
+ }
759
769
  return this.matchNode(this.patternAst, this.ast);
760
770
  }
761
771
 
@@ -41,6 +41,18 @@ export class SubstitutedValue implements Marker {
41
41
  /** Chooses a substituted value's prefix, and says whether it is the source's own. */
42
42
  type MergePrefix = (sourcePrefix: J.Space, templatePrefix: J.Space) => [J.Space, boolean];
43
43
 
44
+ /** Whether `value` is a property, method or spread of an object literal. */
45
+ function isObjectMember(value: unknown): value is J {
46
+ switch (isTree(value) && value.kind) {
47
+ case JS.Kind.PropertyAssignment:
48
+ case JS.Kind.Spread:
49
+ case J.Kind.MethodDeclaration:
50
+ case JS.Kind.ComputedPropertyMethodDeclaration:
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+
44
56
  /**
45
57
  * Visitor that replaces placeholder nodes with actual parameter values.
46
58
  */
@@ -331,6 +343,10 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
331
343
  else if (Array.isArray(param.value)) {
332
344
  arrayToExpand = param.value as J[];
333
345
  }
346
+ // One member of an object literal fills the slot of a member, not the name of a property
347
+ else if (element.kind === JS.Kind.PropertyAssignment && isObjectMember(param.value)) {
348
+ arrayToExpand = [param.value];
349
+ }
334
350
  // Check if it's a CaptureValue
335
351
  else if (param.value instanceof CaptureValue) {
336
352
  const resolved = param.value.resolve(this.values);
@@ -305,7 +305,7 @@ function slotConstraints(parent: J, childId: string): SlotConstraints | undefine
305
305
  case J.Kind.ClassDeclaration:
306
306
  // `class A extends B {}` takes a LeftHandSideExpression
307
307
  return (parent as J.ClassDeclaration).extends?.element?.id === childId ?
308
- {precedence: Precedence.Call} : undefined;
308
+ {precedence: Precedence.Call, typeTree: true} : undefined;
309
309
 
310
310
  case JS.Kind.ExpressionStatement:
311
311
  return (parent as JS.ExpressionStatement).expression?.id === childId ?
@@ -602,7 +602,7 @@ export namespace JS {
602
602
  * Represents the index type in an indexed access.
603
603
  * @example 'key'
604
604
  */
605
- export interface IndexType extends JS {
605
+ export interface IndexType extends JS, Expression, TypeTree {
606
606
  readonly kind: typeof Kind.IndexedAccessTypeIndexType;
607
607
  readonly element: J.RightPadded<TypeTree>;
608
608
  readonly type?: Type;
@@ -633,7 +633,7 @@ export namespace JS {
633
633
  * Represents TypeScript computed property name.
634
634
  * @example { [key]: value }
635
635
  */
636
- export interface ComputedPropertyName extends JS, Expression, TypedTree, VariableDeclarator {
636
+ export interface ComputedPropertyName extends JS, Expression, TypeTree, VariableDeclarator {
637
637
  readonly kind: typeof Kind.ComputedPropertyName;
638
638
  readonly expression: J.RightPadded<Expression>;
639
639
  }
@@ -695,7 +695,7 @@ export namespace JS {
695
695
  * @example data["key"](5, 0, 4)
696
696
  * @example (() => { return 3 + 5 })()
697
697
  */
698
- export interface FunctionCall extends JS, MethodCall {
698
+ export interface FunctionCall extends JS, TypedTree, MethodCall {
699
699
  readonly kind: typeof Kind.FunctionCall;
700
700
  readonly function?: J.RightPadded<Expression>;
701
701
  readonly typeParameters?: J.Container<Expression>;
@@ -706,7 +706,7 @@ export namespace JS {
706
706
  * Represents the `void` operator.
707
707
  * @example void 0;
708
708
  */
709
- export interface Void extends JS, Expression {
709
+ export interface Void extends JS, Expression, Statement {
710
710
  readonly kind: typeof Kind.Void;
711
711
  readonly expression: Expression;
712
712
  }
@@ -737,7 +737,7 @@ export namespace JS {
737
737
  * Represents a method declaration in classes or objects.
738
738
  * @example class A { [Symbol.asyncIterator]() { ... } }
739
739
  */
740
- export interface ComputedPropertyMethodDeclaration extends JS, Statement {
740
+ export interface ComputedPropertyMethodDeclaration extends JS, Statement, TypedTree {
741
741
  readonly kind: typeof Kind.ComputedPropertyMethodDeclaration;
742
742
  readonly leadingAnnotations: J.Annotation[];
743
743
  readonly modifiers: J.Modifier[];
@@ -754,7 +754,7 @@ export namespace JS {
754
754
  * @example for (const x of arr) { }
755
755
  * @example for await (const x of arr) { }
756
756
  */
757
- export interface ForOfLoop extends JS {
757
+ export interface ForOfLoop extends JS, Statement {
758
758
  readonly kind: typeof Kind.ForOfLoop;
759
759
  readonly await?: J.Space;
760
760
  readonly loop: J.ForEachLoop;
@@ -768,7 +768,7 @@ export namespace JS {
768
768
  * Represents a for-in loop.
769
769
  * @example for (let key in obj) { }
770
770
  */
771
- export interface ForInLoop extends JS {
771
+ export interface ForInLoop extends JS, Statement {
772
772
  readonly kind: typeof Kind.ForInLoop;
773
773
  readonly control: ForInLoop.Control;
774
774
  readonly body: J.RightPadded<Statement>;
@@ -802,7 +802,7 @@ export namespace JS {
802
802
  * Represents a literal type object in TypeScript.
803
803
  * @example type T = { a: number };
804
804
  */
805
- export interface TypeLiteral extends JS, TypeTree {
805
+ export interface TypeLiteral extends JS, Expression, TypeTree {
806
806
  readonly kind: typeof Kind.TypeLiteral;
807
807
  readonly members: J.Block;
808
808
  readonly type?: Type;
@@ -812,7 +812,7 @@ export namespace JS {
812
812
  * Represents an array destructuring pattern.
813
813
  * @example const [a, b] = arr;
814
814
  */
815
- export interface ArrayBindingPattern extends JS, TypedTree, VariableDeclarator {
815
+ export interface ArrayBindingPattern extends JS, Expression, TypedTree, VariableDeclarator {
816
816
  readonly kind: typeof Kind.ArrayBindingPattern;
817
817
  readonly elements: J.Container<Expression>;
818
818
  readonly type?: Type;
@@ -822,7 +822,7 @@ export namespace JS {
822
822
  * Represents a named element in a binding pattern.
823
823
  * @example const { x: alias } = obj;
824
824
  */
825
- export interface BindingElement extends JS, Statement, TypeTree {
825
+ export interface BindingElement extends JS, Statement, Expression, TypeTree {
826
826
  readonly kind: typeof Kind.BindingElement;
827
827
  readonly propertyName?: J.RightPadded<Expression>;
828
828
  readonly name: TypedTree;
@@ -858,7 +858,7 @@ export namespace JS {
858
858
  * Represents named exports in an export declaration.
859
859
  * @example export { a, b };
860
860
  */
861
- export interface NamedExports extends JS {
861
+ export interface NamedExports extends JS, Expression {
862
862
  readonly kind: typeof Kind.NamedExports;
863
863
  readonly elements: J.Container<Expression>;
864
864
  readonly type?: Type;
@@ -868,7 +868,7 @@ export namespace JS {
868
868
  * Represents a specifier in a named export.
869
869
  * @example export { foo as bar };
870
870
  */
871
- export interface ExportSpecifier extends JS {
871
+ export interface ExportSpecifier extends JS, Expression, TypedTree {
872
872
  readonly kind: typeof Kind.ExportSpecifier;
873
873
  readonly typeOnly: J.LeftPadded<boolean>;
874
874
  readonly specifier: Expression;
@@ -16,11 +16,13 @@
16
16
  * limitations under the License.
17
17
  */
18
18
  import {mapAsync, updateIfChanged} from "../util";
19
+ import {Markers} from "../markers";
19
20
  import {SourceFile} from "../tree";
20
21
  import {ValidRecipeReturnType} from "../visitor";
21
22
  import {Expression, J, JavaVisitor, NameTree, Statement, Type, TypedTree} from "../java";
22
23
  import {create, Draft} from "mutative";
23
24
  import {isJavaScript, JS, JSX} from "./tree";
25
+ import {isStatement} from "./parser-utils";
24
26
 
25
27
  export class JavaScriptVisitor<P> extends JavaVisitor<P> {
26
28
 
@@ -111,6 +113,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
111
113
  }
112
114
 
113
115
  protected async visitAs(as_: JS.As, p: P): Promise<J | undefined> {
116
+ const expression = await this.visitExpression(as_, p);
117
+ if (!expression?.kind || expression.kind !== JS.Kind.As) {
118
+ return expression;
119
+ }
120
+ as_ = expression as JS.As;
121
+
114
122
  const updates: any = {
115
123
  prefix: await this.visitSpace(as_.prefix, p),
116
124
  markers: await this.visitMarkers(as_.markers, p),
@@ -138,6 +146,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
138
146
  }
139
147
 
140
148
  protected async visitJsxTag(element: JSX.Tag, p: P): Promise<J | undefined> {
149
+ const expression = await this.visitExpression(element, p);
150
+ if (!expression?.kind || expression.kind !== JS.Kind.JsxTag) {
151
+ return expression;
152
+ }
153
+ element = expression as JSX.Tag;
154
+
141
155
  const updates: any = {
142
156
  prefix: await this.visitSpace(element.prefix, p),
143
157
  markers: await this.visitMarkers(element.markers, p),
@@ -174,6 +188,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
174
188
  }
175
189
 
176
190
  protected async visitJsxEmbeddedExpression(expr: JSX.EmbeddedExpression, p: P): Promise<J | undefined> {
191
+ const expression = await this.visitExpression(expr, p);
192
+ if (!expression?.kind || expression.kind !== JS.Kind.JsxEmbeddedExpression) {
193
+ return expression;
194
+ }
195
+ expr = expression as JSX.EmbeddedExpression;
196
+
177
197
  const updates: any = {
178
198
  prefix: await this.visitSpace(expr.prefix, p),
179
199
  markers: await this.visitMarkers(expr.markers, p),
@@ -258,6 +278,16 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
258
278
  markers: await this.visitMarkers(expressionStatement.markers, p),
259
279
  expression: await this.visitDefined<Expression>(expressionStatement.expression, p)
260
280
  };
281
+ // Outside a `for` head the parser makes a statement the statement itself, so a visit that makes one does too
282
+ const visited: J | undefined = updates.expression;
283
+ if (visited && visited !== expressionStatement.expression && isStatement(visited) &&
284
+ this.cursor.parentTree()?.value.kind !== J.Kind.ForLoopControl) {
285
+ return {
286
+ ...visited,
287
+ prefix: concatenated(updates.prefix, visited.prefix),
288
+ markers: withOuterMarkers(updates.markers, visited.markers)
289
+ };
290
+ }
261
291
  return updateIfChanged(expressionStatement, updates);
262
292
  }
263
293
 
@@ -455,6 +485,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
455
485
  }
456
486
 
457
487
  protected async visitBinaryExtensions(jsBinary: JS.Binary, p: P): Promise<J | undefined> {
488
+ const expression = await this.visitExpression(jsBinary, p);
489
+ if (!expression?.kind || expression.kind !== JS.Kind.Binary) {
490
+ return expression;
491
+ }
492
+ jsBinary = expression as JS.Binary;
493
+
458
494
  const updates: any = {
459
495
  prefix: await this.visitSpace(jsBinary.prefix, p),
460
496
  markers: await this.visitMarkers(jsBinary.markers, p),
@@ -504,6 +540,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
504
540
  }
505
541
 
506
542
  protected async visitMappedTypeKeysRemapping(keysRemapping: JS.MappedType.KeysRemapping, p: P): Promise<J | undefined> {
543
+ const statement = await this.visitStatement(keysRemapping, p);
544
+ if (!statement?.kind || statement.kind !== JS.Kind.MappedTypeKeysRemapping) {
545
+ return statement;
546
+ }
547
+ keysRemapping = statement as JS.MappedType.KeysRemapping;
548
+
507
549
  const updates: any = {
508
550
  prefix: await this.visitSpace(keysRemapping.prefix, p),
509
551
  markers: await this.visitMarkers(keysRemapping.markers, p),
@@ -514,6 +556,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
514
556
  }
515
557
 
516
558
  protected async visitMappedTypeParameter(mappedTypeParameter: JS.MappedType.Parameter, p: P): Promise<J | undefined> {
559
+ const statement = await this.visitStatement(mappedTypeParameter, p);
560
+ if (!statement?.kind || statement.kind !== JS.Kind.MappedTypeParameter) {
561
+ return statement;
562
+ }
563
+ mappedTypeParameter = statement as JS.MappedType.Parameter;
564
+
517
565
  const updates: any = {
518
566
  prefix: await this.visitSpace(mappedTypeParameter.prefix, p),
519
567
  markers: await this.visitMarkers(mappedTypeParameter.markers, p),
@@ -767,6 +815,18 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
767
815
  }
768
816
 
769
817
  protected async visitAssignmentOperationExtensions(assignmentOperation: JS.AssignmentOperation, p: P): Promise<J | undefined> {
818
+ const expression = await this.visitExpression(assignmentOperation, p);
819
+ if (!expression?.kind || expression.kind !== JS.Kind.AssignmentOperation) {
820
+ return expression;
821
+ }
822
+ assignmentOperation = expression as JS.AssignmentOperation;
823
+
824
+ const statement = await this.visitStatement(assignmentOperation, p);
825
+ if (!statement?.kind || statement.kind !== JS.Kind.AssignmentOperation) {
826
+ return statement;
827
+ }
828
+ assignmentOperation = statement as JS.AssignmentOperation;
829
+
770
830
  const updates: any = {
771
831
  prefix: await this.visitSpace(assignmentOperation.prefix, p),
772
832
  markers: await this.visitMarkers(assignmentOperation.markers, p),
@@ -796,6 +856,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
796
856
  }
797
857
 
798
858
  protected async visitIndexedAccessTypeIndexType(indexType: JS.IndexedAccessType.IndexType, p: P): Promise<J | undefined> {
859
+ const expression = await this.visitExpression(indexType, p);
860
+ if (!expression?.kind || expression.kind !== JS.Kind.IndexedAccessTypeIndexType) {
861
+ return expression;
862
+ }
863
+ indexType = expression as JS.IndexedAccessType.IndexType;
864
+
799
865
  const updates: any = {
800
866
  prefix: await this.visitSpace(indexType.prefix, p),
801
867
  markers: await this.visitMarkers(indexType.markers, p),
@@ -989,6 +1055,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
989
1055
  }
990
1056
 
991
1057
  protected async visitForOfLoop(forOfLoop: JS.ForOfLoop, p: P): Promise<J | undefined> {
1058
+ const statement = await this.visitStatement(forOfLoop, p);
1059
+ if (!statement?.kind || statement.kind !== JS.Kind.ForOfLoop) {
1060
+ return statement;
1061
+ }
1062
+ forOfLoop = statement as JS.ForOfLoop;
1063
+
992
1064
  const updates: any = {
993
1065
  prefix: await this.visitSpace(forOfLoop.prefix, p),
994
1066
  markers: await this.visitMarkers(forOfLoop.markers, p),
@@ -999,6 +1071,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
999
1071
  }
1000
1072
 
1001
1073
  protected async visitForInLoop(forInLoop: JS.ForInLoop, p: P): Promise<J | undefined> {
1074
+ const statement = await this.visitStatement(forInLoop, p);
1075
+ if (!statement?.kind || statement.kind !== JS.Kind.ForInLoop) {
1076
+ return statement;
1077
+ }
1078
+ forInLoop = statement as JS.ForInLoop;
1079
+
1002
1080
  const updates: any = {
1003
1081
  prefix: await this.visitSpace(forInLoop.prefix, p),
1004
1082
  markers: await this.visitMarkers(forInLoop.markers, p),
@@ -1027,6 +1105,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
1027
1105
  }
1028
1106
 
1029
1107
  protected async visitTypeLiteral(typeLiteral: JS.TypeLiteral, p: P): Promise<J | undefined> {
1108
+ const expression = await this.visitExpression(typeLiteral, p);
1109
+ if (!expression?.kind || expression.kind !== JS.Kind.TypeLiteral) {
1110
+ return expression;
1111
+ }
1112
+ typeLiteral = expression as JS.TypeLiteral;
1113
+
1030
1114
  const updates: any = {
1031
1115
  prefix: await this.visitSpace(typeLiteral.prefix, p),
1032
1116
  markers: await this.visitMarkers(typeLiteral.markers, p),
@@ -1037,6 +1121,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
1037
1121
  }
1038
1122
 
1039
1123
  protected async visitArrayBindingPattern(arrayBindingPattern: JS.ArrayBindingPattern, p: P): Promise<J | undefined> {
1124
+ const expression = await this.visitExpression(arrayBindingPattern, p);
1125
+ if (!expression?.kind || expression.kind !== JS.Kind.ArrayBindingPattern) {
1126
+ return expression;
1127
+ }
1128
+ arrayBindingPattern = expression as JS.ArrayBindingPattern;
1129
+
1040
1130
  const updates: any = {
1041
1131
  prefix: await this.visitSpace(arrayBindingPattern.prefix, p),
1042
1132
  markers: await this.visitMarkers(arrayBindingPattern.markers, p),
@@ -1099,6 +1189,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
1099
1189
  }
1100
1190
 
1101
1191
  protected async visitNamedExports(namedExports: JS.NamedExports, p: P): Promise<J | undefined> {
1192
+ const expression = await this.visitExpression(namedExports, p);
1193
+ if (!expression?.kind || expression.kind !== JS.Kind.NamedExports) {
1194
+ return expression;
1195
+ }
1196
+ namedExports = expression as JS.NamedExports;
1197
+
1102
1198
  const updates: any = {
1103
1199
  prefix: await this.visitSpace(namedExports.prefix, p),
1104
1200
  markers: await this.visitMarkers(namedExports.markers, p),
@@ -1109,6 +1205,12 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
1109
1205
  }
1110
1206
 
1111
1207
  protected async visitExportSpecifier(exportSpecifier: JS.ExportSpecifier, p: P): Promise<J | undefined> {
1208
+ const expression = await this.visitExpression(exportSpecifier, p);
1209
+ if (!expression?.kind || expression.kind !== JS.Kind.ExportSpecifier) {
1210
+ return expression;
1211
+ }
1212
+ exportSpecifier = expression as JS.ExportSpecifier;
1213
+
1112
1214
  const updates: any = {
1113
1215
  prefix: await this.visitSpace(exportSpecifier.prefix, p),
1114
1216
  markers: await this.visitMarkers(exportSpecifier.markers, p),
@@ -1203,3 +1305,21 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
1203
1305
  return super.accept(j, p);
1204
1306
  }
1205
1307
  }
1308
+
1309
+ /** The prefix that prints as `outer` and then `inner`. */
1310
+ function concatenated(outer: J.Space, inner: J.Space): J.Space {
1311
+ if (outer.comments.length === 0) {
1312
+ return {...inner, whitespace: outer.whitespace + inner.whitespace};
1313
+ }
1314
+ const last = outer.comments[outer.comments.length - 1];
1315
+ return {
1316
+ ...outer,
1317
+ comments: [...outer.comments.slice(0, -1), {...last, suffix: last.suffix + inner.whitespace}, ...inner.comments]
1318
+ };
1319
+ }
1320
+
1321
+ /** `inner`, led by each marker of `outer` it does not already hold, so they print as they did around it. */
1322
+ function withOuterMarkers(outer: Markers, inner: Markers): Markers {
1323
+ const added = outer.markers.filter(marker => !inner.markers.some(own => own.id === marker.id));
1324
+ return added.length === 0 ? inner : {...inner, markers: [...added, ...inner.markers]};
1325
+ }
package/src/recipe.ts CHANGED
@@ -175,6 +175,11 @@ export interface OptionDescriptor {
175
175
  readonly type?: string
176
176
  }
177
177
 
178
+ /**
179
+ * @typeParam P The type of the accumulator where scanning data is held until the editing phase. It stays in memory
180
+ * until the cycle ends, so it should hold values such as coordinates, paths and ids, not trees or other objects of a
181
+ * type given to registerHeavyweight.
182
+ */
178
183
  export abstract class ScanningRecipe<P> extends Recipe {
179
184
  private readonly recipeAccMessage = Symbol("org.openrewrite.recipe.acc");
180
185
 
package/src/run.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import {ExecutionContext} from "./execution";
17
- import {rootCursor, SourceFile} from "./tree";
17
+ import {Cursor, rootCursor, SourceFile} from "./tree";
18
18
  import {createTwoFilesPatch} from "diff";
19
19
  import {TreePrinters} from "./print";
20
20
  import {Recipe, ScanningRecipe} from "./recipe";
@@ -83,6 +83,25 @@ async function recursiveOnComplete(recipe: Recipe, ctx: ExecutionContext, recipe
83
83
  }
84
84
  }
85
85
 
86
+ /**
87
+ * Under this key a test harness puts an {@link AccumulatorListener} on the execution context, which the scheduler
88
+ * calls with the accumulator of each scanning recipe after the scanning phase and again after the editing phase.
89
+ */
90
+ export const ACCUMULATOR_LISTENER = "org.openrewrite.test.accumulatorListener";
91
+
92
+ export type AccumulatorListener =
93
+ (accumulators: Map<ScanningRecipe<any>, any>, phase: "after scanning" | "after editing") => void;
94
+
95
+ async function accumulators(recipe: Recipe, recipeLists: RecipeLists, cursor: Cursor,
96
+ ctx: ExecutionContext): Promise<Map<ScanningRecipe<any>, any>> {
97
+ return (await recurseRecipeList(recipe, new Map<ScanningRecipe<any>, any>(), recipeLists, async (r, found) => {
98
+ if (r instanceof ScanningRecipe && !found.has(r)) {
99
+ found.set(r, r.accumulator(cursor, ctx));
100
+ }
101
+ return found;
102
+ }))!;
103
+ }
104
+
86
105
  export async function scheduleRun(recipe: Recipe, before: SourceFile[], ctx: ExecutionContext): Promise<RecipeRun> {
87
106
  const changeset: Result[] = [];
88
107
  for await (const result of scheduleRunStreaming(recipe, before, ctx)) {
@@ -135,13 +154,21 @@ export async function* scheduleRunStreaming(
135
154
  // of the recipe list, leaving nested scanners unrun.
136
155
  await recurseRecipeList(recipe, b, recipeLists, async (recipe, b2) => {
137
156
  if (recipe instanceof ScanningRecipe) {
138
- await (await recipe.scanner(recipe.accumulator(cursor, ctx))).visit(b2, ctx, cursor);
157
+ const scanned = await (await recipe.scanner(recipe.accumulator(cursor, ctx))).visit(b2, ctx, cursor);
158
+ if (scanned !== b2 && ctx.getBoolean(ExecutionContext.SCANNING_MUTATION_VALIDATION, false)) {
159
+ throw new Error("Record what the scanner finds in the accumulator, and make the edits in " +
160
+ `editorWithData(acc). ${recipe.name}'s scanner ${scanned ? "changed" : "deleted"} ` +
161
+ `${b2.sourcePath}, and the scanning phase discards edits. A scanner that reuses an ` +
162
+ "editing visitor opts out with spec.checkNoEditsInScanningPhase = false.");
163
+ }
139
164
  }
140
165
  return b2;
141
166
  });
142
167
  }
143
168
 
144
169
  const totalFiles = files.length;
170
+ const listener: AccumulatorListener | undefined = ctx.messages[ACCUMULATOR_LISTENER];
171
+ listener?.(await accumulators(recipe, recipeLists, cursor, ctx), "after scanning");
145
172
 
146
173
  // Phase 2: Collect generated files
147
174
  const generated = (await recurseRecipeList(recipe, [] as SourceFile[], recipeLists, async (recipe, generated) => {
@@ -169,6 +196,7 @@ export async function* scheduleRunStreaming(
169
196
  yield new Result(undefined, editedG);
170
197
  }
171
198
  }
199
+ listener?.(await accumulators(recipe, recipeLists, cursor, ctx), "after editing");
172
200
  } else {
173
201
  // For non-scanning recipes, process files immediately as they come in
174
202
  let processCount = 0;