Skip to content

Commit

Permalink
Add new JS.ScopedVariableDeclarations for variables
Browse files Browse the repository at this point in the history
JS/TS variables are typically introduced by a `let`, `const`, or `var` keyword (not for parameters or fields, however). This and the fact that for multi-variable declarations each variable can declare its own type (unlike in Java), requires us to add a JS-specific construct.
  • Loading branch information
knutwannheden committed Oct 3, 2024
1 parent 146e347 commit e6c99ba
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 9 deletions.
15 changes: 7 additions & 8 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,7 @@ export class JavaScriptParserVisitor {
}

visitVariableDeclaration(node: ts.VariableDeclaration) {
// FIXME possibly we should include the `J.VariableDeclarations` at this point
return new J.VariableDeclarations.NamedVariable(
randomId(),
this.prefix(node),
Expand All @@ -1379,17 +1380,15 @@ export class JavaScriptParserVisitor {
}

visitVariableDeclarationList(node: ts.VariableDeclarationList) {
return new J.VariableDeclarations(
const kind = node.getFirstToken(this.sourceFile);
return new JS.ScopedVariableDeclarations(
randomId(),
this.prefix(node),
Markers.EMPTY,
[],
this.mapModifiers(node),
node.declarations[0].type ? this.visit(node.declarations[0].type) : null,
null,
[],
this.rightPaddedList([...node.declarations], this.suffix)
);
kind?.kind === ts.SyntaxKind.LetKeyword ? JS.ScopedVariableDeclarations.Scope.Let :
kind?.kind === ts.SyntaxKind.ConstKeyword ? JS.ScopedVariableDeclarations.Scope.Const : JS.ScopedVariableDeclarations.Scope.Var,
node.declarations.map(declaration => this.rightPadded(this.visit(declaration), this.suffix(declaration)))
)
}

visitFunctionDeclaration(node: ts.FunctionDeclaration) {
Expand Down
2 changes: 2 additions & 0 deletions openrewrite/src/javascript/tree/support_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export namespace JsSpace {
OBJECT_BINDING_DECLARATIONS_BINDING_PREFIX,
OBJECT_BINDING_DECLARATIONS_PREFIX,
PROPERTY_ASSIGNMENT_PREFIX,
SCOPED_VARIABLE_DECLARATIONS_PREFIX,
TEMPLATE_EXPRESSION_PREFIX,
TEMPLATE_EXPRESSION_VALUE_AFTER,
TEMPLATE_EXPRESSION_VALUE_PREFIX,
Expand Down Expand Up @@ -234,6 +235,7 @@ export namespace JsRightPadded {
JS_IMPORT_NAME,
OBJECT_BINDING_DECLARATIONS_BINDING_PROPERTY_NAME,
PROPERTY_ASSIGNMENT_NAME,
SCOPED_VARIABLE_DECLARATIONS_VARIABLES,
TEMPLATE_EXPRESSION_TAG,
UNION_TYPES,
}
Expand Down
89 changes: 89 additions & 0 deletions openrewrite/src/javascript/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,95 @@ export class PropertyAssignment extends JSMixin(Object) implements Statement, Ty

}

@LstType("org.openrewrite.javascript.tree.JS$ScopedVariableDeclarations")
export class ScopedVariableDeclarations extends JSMixin(Object) implements Statement {
public constructor(id: UUID, prefix: Space, markers: Markers, scope: ScopedVariableDeclarations.Scope | null, variables: JRightPadded<Expression>[]) {
super();
this._id = id;
this._prefix = prefix;
this._markers = markers;
this._scope = scope;
this._variables = variables;
}

private readonly _id: UUID;

public get id(): UUID {
return this._id;
}

public withId(id: UUID): ScopedVariableDeclarations {
return id === this._id ? this : new ScopedVariableDeclarations(id, this._prefix, this._markers, this._scope, this._variables);
}

private readonly _prefix: Space;

public get prefix(): Space {
return this._prefix;
}

public withPrefix(prefix: Space): ScopedVariableDeclarations {
return prefix === this._prefix ? this : new ScopedVariableDeclarations(this._id, prefix, this._markers, this._scope, this._variables);
}

private readonly _markers: Markers;

public get markers(): Markers {
return this._markers;
}

public withMarkers(markers: Markers): ScopedVariableDeclarations {
return markers === this._markers ? this : new ScopedVariableDeclarations(this._id, this._prefix, markers, this._scope, this._variables);
}

private readonly _scope: ScopedVariableDeclarations.Scope | null;

public get scope(): ScopedVariableDeclarations.Scope | null {
return this._scope;
}

public withScope(scope: ScopedVariableDeclarations.Scope | null): ScopedVariableDeclarations {
return scope === this._scope ? this : new ScopedVariableDeclarations(this._id, this._prefix, this._markers, scope, this._variables);
}

private readonly _variables: JRightPadded<Expression>[];

public get variables(): Expression[] {
return JRightPadded.getElements(this._variables);
}

public withVariables(variables: Expression[]): ScopedVariableDeclarations {
return this.padding.withVariables(JRightPadded.withElements(this._variables, variables));
}

public acceptJavaScript<P>(v: JavaScriptVisitor<P>, p: P): J | null {
return v.visitScopedVariableDeclarations(this, p);
}

get padding() {
const t = this;
return new class {
public get variables(): JRightPadded<Expression>[] {
return t._variables;
}
public withVariables(variables: JRightPadded<Expression>[]): ScopedVariableDeclarations {
return t._variables === variables ? t : new ScopedVariableDeclarations(t._id, t._prefix, t._markers, t._scope, variables);
}
}
}

}

export namespace ScopedVariableDeclarations {
export enum Scope {
Const = 0,
Let = 1,
Var = 2,

}

}

@LstType("org.openrewrite.javascript.tree.JS$TemplateExpression")
export class TemplateExpression extends JSMixin(Object) implements Statement, Expression {
public constructor(id: UUID, prefix: Space, markers: Markers, delimiter: string, tag: JRightPadded<Expression> | null, strings: J[], _type: JavaType | null) {
Expand Down
15 changes: 14 additions & 1 deletion openrewrite/src/javascript/visitor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as extensions from "./extensions";
import {ListUtils, SourceFile, Tree, TreeVisitor} from "../core";
import {JS, isJavaScript, JsLeftPadded, JsRightPadded, JsContainer, JsSpace} from "./tree";
import {CompilationUnit, Alias, ArrowFunction, Await, DefaultType, Delete, Export, ExpressionStatement, FunctionType, JsImport, JsBinary, ObjectBindingDeclarations, PropertyAssignment, StatementExpression, TemplateExpression, Tuple, TypeDeclaration, TypeOf, TypeOperator, Unary, Union, Void, Yield} from "./tree";
import {CompilationUnit, Alias, ArrowFunction, Await, DefaultType, Delete, Export, ExpressionStatement, FunctionType, JsImport, JsBinary, ObjectBindingDeclarations, PropertyAssignment, ScopedVariableDeclarations, StatementExpression, TemplateExpression, Tuple, TypeDeclaration, TypeOf, TypeOperator, Unary, Union, Void, Yield} from "./tree";
import {Expression, J, JContainer, JLeftPadded, JRightPadded, Space, Statement} from "../java/tree";
import {JavaVisitor} from "../java";
import * as Java from "../java/tree";
Expand Down Expand Up @@ -215,6 +215,19 @@ export class JavaScriptVisitor<P> extends JavaVisitor<P> {
return propertyAssignment;
}

public visitScopedVariableDeclarations(scopedVariableDeclarations: ScopedVariableDeclarations, p: P): J | null {
scopedVariableDeclarations = scopedVariableDeclarations.withPrefix(this.visitJsSpace(scopedVariableDeclarations.prefix, JsSpace.Location.SCOPED_VARIABLE_DECLARATIONS_PREFIX, p)!);
let tempStatement = this.visitStatement(scopedVariableDeclarations, p) as Statement;
if (!(tempStatement instanceof ScopedVariableDeclarations))
{
return tempStatement;
}
scopedVariableDeclarations = tempStatement as ScopedVariableDeclarations;
scopedVariableDeclarations = scopedVariableDeclarations.withMarkers(this.visitMarkers(scopedVariableDeclarations.markers, p));
scopedVariableDeclarations = scopedVariableDeclarations.padding.withVariables(ListUtils.map(scopedVariableDeclarations.padding.variables, el => this.visitJsRightPadded(el, JsRightPadded.Location.SCOPED_VARIABLE_DECLARATIONS_VARIABLES, p)));
return scopedVariableDeclarations;
}

public visitStatementExpression(statementExpression: StatementExpression, p: P): J | null {
statementExpression = statementExpression.withStatement(this.visitAndCast(statementExpression.statement, p)!);
return statementExpression;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/openrewrite/javascript/JavaScriptVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,20 @@ public J visitPropertyAssignment(JS.PropertyAssignment propertyAssignment, P p)
return pa;
}

public J visitScopedVariableDeclarations(JS.ScopedVariableDeclarations scopedVariableDeclarations, P p) {
JS.ScopedVariableDeclarations vd = scopedVariableDeclarations;
vd = vd.withPrefix(visitSpace(vd.getPrefix(), JsSpace.Location.SCOPED_VARIABLE_DECLARATIONS_PREFIX, p));
vd = vd.withMarkers(visitMarkers(vd.getMarkers(), p));
Statement temp = (Statement) visitStatement(vd, p);
if (!(temp instanceof JS.ScopedVariableDeclarations)) {
return temp;
} else {
vd = (JS.ScopedVariableDeclarations) temp;
}
vd = vd.getPadding().withVariables(ListUtils.map(vd.getPadding().getVariables(), e -> visitRightPadded(e, JsRightPadded.Location.SCOPED_VARIABLE_DECLARATIONS_VARIABLE, p)));
return vd;
}

public J visitStatementExpression(JS.StatementExpression expression, P p) {
JS.StatementExpression se = expression;
Expression temp = (Expression) visitExpression(se, p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,30 @@ public J visitPropertyAssignment(JS.PropertyAssignment propertyAssignment, Print
return propertyAssignment;
}

@Override
public J visitScopedVariableDeclarations(JS.ScopedVariableDeclarations variableDeclarations, PrintOutputCapture<P> p) {
beforeSyntax(variableDeclarations, Space.Location.VARIABLE_DECLARATIONS_PREFIX, p);

if (variableDeclarations.getScope() != null) {
switch (variableDeclarations.getScope()) {
case Let:
p.append("let");
break;
case Const:
p.append("const");
break;
case Var:
p.append("var");
break;
}
}

visitRightPadded(variableDeclarations.getPadding().getVariables(), JsRightPadded.Location.SCOPED_VARIABLE_DECLARATIONS_VARIABLE, ",", p);

afterSyntax(variableDeclarations, p);
return variableDeclarations;
}

@Override
public J visitObjectBindingDeclarations(JS.ObjectBindingDeclarations objectBindingDeclarations, PrintOutputCapture<P> p) {
beforeSyntax(objectBindingDeclarations, Space.Location.VARIABLE_DECLARATIONS_PREFIX, p);
Expand Down Expand Up @@ -755,6 +779,8 @@ public J visitVariableDeclarations(J.VariableDeclarations multiVariable, PrintOu
public J visitVariable(J.VariableDeclarations.NamedVariable variable, PrintOutputCapture<P> p) {
beforeSyntax(variable, Space.Location.VARIABLE_PREFIX, p);
visit(variable.getName(), p);
JLeftPadded<Expression> initializer = variable.getPadding().getInitializer();
visitLeftPadded("=", initializer, JLeftPadded.Location.VARIABLE_INITIALIZER, p);
afterSyntax(variable, p);
return variable;
}
Expand Down
83 changes: 83 additions & 0 deletions src/main/java/org/openrewrite/javascript/tree/JS.java
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,89 @@ public PropertyAssignment withName(JRightPadded<Expression> target) {
}
}

@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
@RequiredArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class ScopedVariableDeclarations implements JS, Statement {

@Nullable
@NonFinal
transient WeakReference<ScopedVariableDeclarations.Padding> padding;

@Getter
@With
@EqualsAndHashCode.Include
UUID id;

@Getter
@With
Space prefix;

@Getter
@With
Markers markers;

@Getter
@With
@Nullable
Scope scope;

List<JRightPadded<Expression>> variables;

public List<Expression> getVariables() {
return JRightPadded.getElements(variables);
}

public ScopedVariableDeclarations withVariables(List<Expression> variables) {
return getPadding().withVariables(JRightPadded.withElements(this.variables, variables));
}

@Override
public <P> J acceptJavaScript(JavaScriptVisitor<P> v, P p) {
return v.visitScopedVariableDeclarations(this, p);
}

@Override
public CoordinateBuilder.Statement getCoordinates() {
return new CoordinateBuilder.Statement(this);
}

public Padding getPadding() {
Padding p;
if (this.padding == null) {
p = new Padding(this);
this.padding = new WeakReference<>(p);
} else {
p = this.padding.get();
if (p == null || p.t != this) {
p = new Padding(this);
this.padding = new WeakReference<>(p);
}
}
return p;
}

public enum Scope {
Const,
Let,
Var,
}

@RequiredArgsConstructor
public static class Padding {
private final ScopedVariableDeclarations t;

public List<JRightPadded<Expression>> getVariables() {
return t.variables;
}

public ScopedVariableDeclarations withVariables(List<JRightPadded<Expression>> variables) {
return t.variables == variables ? t : new ScopedVariableDeclarations(t.id, t.prefix, t.markers, t.scope, variables);
}
}
}

@Getter
@SuppressWarnings("unchecked")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/openrewrite/javascript/tree/JsSpace.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public enum Location {
OBJECT_BINDING_PREFIX,
PROPERTY_ASSIGNMENT_NAME_SUFFIX,
PROPERTY_ASSIGNMENT_PREFIX,
SCOPED_VARIABLE_DECLARATIONS_PREFIX,
SCOPED_VARIABLE_DECLARATIONS_VARIABLE_SUFFIX,
TAG_SUFFIX,
TEMPLATE_EXPRESSION_PREFIX,
TEMPLATE_EXPRESSION_VALUE_PREFIX,
Expand Down

0 comments on commit e6c99ba

Please sign in to comment.