Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Throwing FlowSyntaxNotSupportedError if meets @flow annotation in a source file #201

Merged
merged 1 commit into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 64 additions & 9 deletions openrewrite/src/javascript/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class JavaScriptParser extends Parser {
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.CommonJS,
allowJs: true,
esModuleInterop: true,
};
}

Expand Down Expand Up @@ -141,21 +142,68 @@ export class JavaScriptParser extends Parser {
for (const input of inputFiles.values()) {
const filePath = input.path;
const sourceFile = program.getSourceFile(filePath);
if (sourceFile) {
try {
const parsed = new JavaScriptParserVisitor(this, sourceFile, typeChecker).visit(sourceFile) as SourceFile;
result.push(parsed.withSourcePath(relativeTo != null ? path.relative(relativeTo, input.path) : input.path));
} catch (error) {
result.push(ParseError.build(this, input, relativeTo, ctx, error instanceof Error ? error : new Error('Parser threw unknown error: ' + error), null));
}
} else {
if (!sourceFile) {
result.push(ParseError.build(this, input, relativeTo, ctx, new Error('Parser returned undefined'), null));
continue;
}
}

if (this.hasFlowAnnotation(sourceFile)) {
result.push(ParseError.build(this, input, relativeTo, ctx, new FlowSyntaxNotSupportedError(`Flow syntax not supported: ${input.path}`), null));
continue;
}

// ToDo: uncomment code after tests fixing
// const syntaxErrors = this.checkSyntaxErrors(program, sourceFile);
// if (syntaxErrors.length > 0) {
// syntaxErrors.forEach(
// e => result.push(ParseError.build(this, input, relativeTo, ctx, new SyntaxError(`Compiler error: ${e[0]} [${e[1]}]`), null))
// );
// continue;
// }

try {
const parsed = new JavaScriptParserVisitor(this, sourceFile, typeChecker).visit(sourceFile) as SourceFile;
result.push(parsed.withSourcePath(relativeTo != null ? path.relative(relativeTo, input.path) : input.path));
} catch (error) {
result.push(ParseError.build(this, input, relativeTo, ctx, error instanceof Error ? error : new Error('Parser threw unknown error: ' + error), null));
}
}
return result;
}

private checkSyntaxErrors(program: ts.Program, sourceFile: ts.SourceFile) {
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
// checking Parsing and Syntax Errors
let syntaxErrors : [errorMsg: string, errorCode: number][] = [];
if (diagnostics.length > 0) {
const errors = diagnostics.filter(d => d.code >= 1000 && d.code < 2000);
if (errors.length > 0) {
syntaxErrors = errors.map(e => {
let errorMsg;
if (e.file) {
let {line, character} = ts.getLineAndCharacterOfPosition(e.file, e.start!);
let message = ts.flattenDiagnosticMessageText(e.messageText, "\n");
errorMsg = `${e.file.fileName} (${line + 1},${character + 1}): ${message}`;
} else {
errorMsg = ts.flattenDiagnosticMessageText(e.messageText, "\n");
}
return [errorMsg, e.code];
});
}
}
return syntaxErrors;
}

private hasFlowAnnotation(sourceFile: ts.SourceFile) {
if (sourceFile.fileName.endsWith('.js') || sourceFile.fileName.endsWith('.jsx')) {
const comments = sourceFile.getFullText().match(/\/\*[\s\S]*?\*\/|\/\/.*(?=[\r\n])/g);
if (comments) {
return comments.some(comment => comment.includes("@flow"));
}
}
return false;
}

accept(path: string): boolean {
return path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js') || path.endsWith('.jsx');
}
Expand Down Expand Up @@ -4094,3 +4142,10 @@ function prefixFromNode(node: ts.Node, sourceFile: ts.SourceFile): Space {
// Step 4: Return the Space object with comments and leading whitespace
return new Space(comments, whitespace.length > 0 ? whitespace : null);
}

class FlowSyntaxNotSupportedError extends SyntaxError {
constructor(message: string = "Flow syntax is not supported") {
super(message);
this.name = "FlowSyntaxNotSupportedError";
}
}
2 changes: 1 addition & 1 deletion openrewrite/test/javascript/parser/empty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('empty mapping', () => {
test('simple', () => {
rewriteRun(
//language=typescript
typeScript('if (true) ;')
typeScript('if (true) {/*a*/};')
);
});
});
49 changes: 49 additions & 0 deletions openrewrite/test/javascript/parser/flowAnnotation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {connect, disconnect, javaScript, rewriteRun, typeScript} from '../testHarness';

describe('flow annotation checking test', () => {
beforeAll(() => connect());
afterAll(() => disconnect());

test('@flow in a comment in js', () => {
const faultyTest = () => rewriteRun(
//language=javascript
javaScript(`
//@flow

import Rocket from './rocket';
import RocketLaunch from './rocket-launch';
`)
);

expect(faultyTest).toThrow(/FlowSyntaxNotSupportedError/);
});

test('@flow in a multiline comment in js', () => {
const faultyTest = () => rewriteRun(
//language=javascript
javaScript(`
/*
@flow
*/

import Rocket from './rocket';
import RocketLaunch from './rocket-launch';
`)
);

expect(faultyTest).toThrow(/FlowSyntaxNotSupportedError/);
});

test('@flow in a comment in ts', () => {
rewriteRun(
//language=typescript
typeScript(`
//@flow

import Rocket from './rocket';
import RocketLaunch from './rocket-launch';
`)
);
});

});
Loading