Skip to content

Commit

Permalink
Basic support for functions
Browse files Browse the repository at this point in the history
  • Loading branch information
knutwannheden committed Sep 29, 2024
1 parent 557ed4b commit 5812aa9
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 20 deletions.
76 changes: 71 additions & 5 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export class JavaScriptParserVisitor {
);
}

private mapModifiers(node: ts.VariableStatement | ts.ClassDeclaration | ts.PropertyDeclaration) {
private mapModifiers(node: ts.VariableStatement | ts.ClassDeclaration | ts.PropertyDeclaration | ts.FunctionDeclaration | ts.ParameterDeclaration) {
if (ts.isVariableStatement(node)) {
return [new J.Modifier(
randomId(),
Expand All @@ -253,6 +253,8 @@ export class JavaScriptParserVisitor {
return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : [];
} else if (ts.isPropertyDeclaration(node)) {
return []; // FIXME
} else if (ts.isFunctionDeclaration(node) || ts.isParameter(node)) {
return node.modifiers ? node.modifiers?.filter(ts.isModifier).map(this.mapModifier) : [];
}
throw new Error(`Cannot get modifiers from ${node}`);
}
Expand Down Expand Up @@ -341,6 +343,10 @@ export class JavaScriptParserVisitor {
}

visitClassDeclaration(node: ts.ClassDeclaration) {
if (node.modifiers?.find(ts.isDecorator) || node.typeParameters) {
return this.visitUnknown(node);
}

return new J.ClassDeclaration(
randomId(),
this.prefix(node),
Expand Down Expand Up @@ -520,7 +526,28 @@ export class JavaScriptParserVisitor {
}

visitParameter(node: ts.ParameterDeclaration) {
return this.visitUnknown(node);
return new J.VariableDeclarations(
randomId(),
this.prefix(node),
Markers.EMPTY,
[],
this.mapModifiers(node),
node.type ? this.visit(node.type) : null,
null,
[],
[this.rightPadded(
new J.VariableDeclarations.NamedVariable(
randomId(),
this.prefix(node.name),
Markers.EMPTY,
this.visit(node.name),
[],
node.initializer ? this.leftPadded(this.prefix(node.getChildAt(node.getChildCount() - 2)), this.visit(node.initializer)) : null,
this.mapVariableType(node)
),
this.suffix(node.name)
)]
);
}

visitDecorator(node: ts.Decorator) {
Expand Down Expand Up @@ -1124,7 +1151,14 @@ export class JavaScriptParserVisitor {
}

visitBlock(node: ts.Block) {
return this.visitUnknown(node);
return new J.Block(
randomId(),
this.prefix(node),
Markers.EMPTY,
this.rightPadded(false, Space.EMPTY),
this.semicolonPaddedStatementList(node.statements),
this.prefix(node.getLastToken()!)
);
}

visitEmptyStatement(node: ts.EmptyStatement) {
Expand Down Expand Up @@ -1235,7 +1269,39 @@ export class JavaScriptParserVisitor {
}

visitFunctionDeclaration(node: ts.FunctionDeclaration) {
return this.visitUnknown(node);
if (node.modifiers?.find(ts.isDecorator) || node.typeParameters) {
return this.visitUnknown(node);
}

return new J.MethodDeclaration(
randomId(),
this.prefix(node),
Markers.EMPTY,
[], // FIXME decorators
[new J.Modifier(
randomId(),
Space.EMPTY,
Markers.EMPTY,
"function",
J.Modifier.Type.LanguageExtension,
[]
), ...this.mapModifiers(node)],
null, // FIXME type parameters
node.type ? this.visit(node.type) : null,
new J.MethodDeclaration.IdentifierWithAnnotations(
node.name ? this.visit(node.name) : this.mapIdentifier(node, ""),
[]
),
new JContainer(
this.prefix(node.getChildAt(2)),
this.rightPaddedList([...node.parameters], this.suffix),
Markers.EMPTY
),
null,
node.body ? this.convert<J.Block>(node.body) : null,
null,
this.mapMethodType(node)
);
}

visitInterfaceDeclaration(node: ts.InterfaceDeclaration) {
Expand Down Expand Up @@ -1662,7 +1728,7 @@ export class JavaScriptParserVisitor {
// return Space.format(this.sourceFile.text, node.getFullStart(), node.getFullStart() + node.getLeadingTriviaWidth());
}

private suffix(node: ts.Node): Space {
private suffix = (node: ts.Node): Space => {
return this.prefix(getNextSibling(node)!);
}

Expand Down
16 changes: 15 additions & 1 deletion openrewrite/test/javascript/parser/class.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {connect, disconnect, rewriteRun, typeScript} from '../testHarness';
import {connect, disconnect, rewriteRun, rewriteRunWithOptions, typeScript} from '../testHarness';

describe('class mapping', () => {
beforeAll(() => connect());
Expand All @@ -10,6 +10,20 @@ describe('class mapping', () => {
typeScript('class A {}')
);
});
test('decorator', () => {
rewriteRunWithOptions(
{expectUnknowns: true},
//language=typescript
typeScript('@foo class A {}')
);
});
test('type parameter', () => {
rewriteRunWithOptions(
{expectUnknowns: true},
//language=typescript
typeScript('class A<T> {}')
);
});
test('body', () => {
rewriteRun(
//language=typescript
Expand Down
43 changes: 43 additions & 0 deletions openrewrite/test/javascript/parser/function.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {connect, disconnect, rewriteRun, rewriteRunWithOptions, typeScript} from '../testHarness';

describe('class mapping', () => {
beforeAll(() => connect());
afterAll(() => disconnect());

test('simple', () => {
rewriteRun(
//language=typescript
typeScript('function f () { }')
);
});
test('single parameter', () => {
rewriteRun(
//language=typescript
typeScript('function f(a) {}')
);
});
test('single typed parameter', () => {
rewriteRun(
//language=typescript
typeScript('function f(a : number) {}')
);
});
test('single typed parameter with initializer', () => {
rewriteRun(
//language=typescript
typeScript('function f(a : number = 2) {}')
);
});
test('single parameter with initializer', () => {
rewriteRun(
//language=typescript
typeScript('function f(a = 2) {}')
);
});
test('two parameters', () => {
rewriteRun(
//language=typescript
typeScript('function f(a = 2 , b) {}')
);
});
});
29 changes: 15 additions & 14 deletions openrewrite/test/javascript/testHarness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {JavaScriptParser, JavaScriptVisitor} from "../../dist/src/javascript";
export interface RewriteTestOptions {
normalizeIndent?: boolean
validatePrintIdempotence?: boolean
allowUnknowns?: boolean
expectUnknowns?: boolean
}

export type SourceSpec = (options: RewriteTestOptions) => void;
Expand Down Expand Up @@ -92,21 +92,22 @@ function sourceFile(before: string, defaultPath: string, spec?: (sourceFile: JS.
if (isParseError(sourceFile)) {
throw new Error(`Parsing failed for ${sourceFile.sourcePath}: ${sourceFile.markers.findFirst(ParseExceptionResult)!.message}`);
}
if (!(options.allowUnknowns ?? false)) {
try {
let unknowns: J.Unknown[] = [];
new class extends JavaScriptVisitor<number> {
visitUnknown(unknown: J.Unknown, p: number): J.J | null {
unknowns.push(unknown);
return unknown;
}
}().visit(sourceFile, 0);
if (unknowns.length != 0) {
throw new Error("No J.Unknown instances were expected: " + unknowns.map(u => u.source.text));
try {
let unknowns: J.Unknown[] = [];
new class extends JavaScriptVisitor<number> {
visitUnknown(unknown: J.Unknown, p: number): J.J | null {
unknowns.push(unknown);
return unknown;
}
} catch (e) {
throw e instanceof RecipeRunException ? e.cause : e;
}().visit(sourceFile, 0);
const expectUnknowns = options.expectUnknowns ?? false;
if (expectUnknowns && unknowns.length == 0) {
throw new Error("No J.Unknown instances were found. Adjust the test expectation.");
} else if (!expectUnknowns && unknowns.length != 0) {
throw new Error("No J.Unknown instances were expected: " + unknowns.map(u => u.source.text));
}
} catch (e) {
throw e instanceof RecipeRunException ? e.cause : e;
}
if (options.validatePrintIdempotence ?? true) {
let printed = print(sourceFile);
Expand Down

0 comments on commit 5812aa9

Please sign in to comment.