Skip to content

SONARARMOR-860 Parser should serialize popular syntax #5257

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 31, 2025

Conversation

roberto-orlandi-sonarsource
Copy link
Contributor

@roberto-orlandi-sonarsource roberto-orlandi-sonarsource commented Mar 28, 2025

SONARARMOR-860

Part of

@@ -262,15 +263,6 @@ describe('ast', () => {
checkAstIsProperlySerializedAndDeserialized(ast as TSESTree.Program, protoMessage, 'foo.ts');
});

test('Unknown node types in program body are not serialized', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was previously implemented to check that from a list of nodes we are able to filter out the unknown ones. With this PR we implement basic support for some nodes previously unsupported. After this, I could not find another test code to use to reproduce this scenario, so I removed this test, while I kept the business logic, as I am sure that it is still a possible scenario in production.

@@ -673,7 +673,7 @@ public record TSModuleDeclaration(
Optional<TSModuleBlock> body,
String kind
)
implements ModuleDeclaration {}
implements Declaration {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes an error in the hierarchy introduced during the previous PR to support TSModuleDeclaration module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by this comment in the previous PR, I added some UTs in this class, to check that given a piece of code we create the expected java nodes.

@roberto-orlandi-sonarsource
Copy link
Contributor Author

This PR contains the following:

  • Implement basic support for some of the most popular TS unsupported nodes, where basic support means that we serialize and deserialize them as empty objects, with the correct type. This will prevent us from stopping the analysis on SonarJasmin, when those nodes are encountered
  • Fixes an error introduced in the ESTree.java hierarchy during a previous PR: The TSModuleDeclaration should be a subclass of Declaration and not of ModuleDeclaration. This was spotted because we would fail handling code such as export namespace A {}, because the export syntax node expects a Declaration as a child.
  • A few more UTs added in the StandaloneParserTest, inspired by a previous PR's comment

@roberto-orlandi-sonarsource roberto-orlandi-sonarsource marked this pull request as ready for review March 31, 2025 06:04
@roberto-orlandi-sonarsource roberto-orlandi-sonarsource requested a review from a team March 31, 2025 06:07
Comment on lines 319 to 335
test('should serialize some nodes to empty object', async () => {
const codeMap = new Map<string, string>();
codeMap.set('TSTypeAliasDeclaration', `type A = { a: string };`);
codeMap.set('TSInterfaceDeclaration', `interface A { a: string; }`);
codeMap.set('TSEnumDeclaration', `enum Direction {}`);
codeMap.set('TSDeclareFunction', `declare function foo()`);

for (const [nodeType, code] of codeMap) {
const ast = await parseSourceCode(code, parsersMap.typescript);
const protoMessage = visitNode(ast as TSESTree.Program);

expect(protoMessage.program.body[0].type).toEqual(NODE_TYPE_ENUM.values[`${nodeType}Type`]);
const tSModuleDeclaration = protoMessage.program.body[0][lowerCaseFirstLetter(nodeType)];
expect(tSModuleDeclaration).toEqual({});
checkAstIsProperlySerializedAndDeserialized(ast as TSESTree.Program, protoMessage, 'foo.ts');
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better practice to implement would be to run the test in a loop. Sth like this:

Suggested change
test('should serialize some nodes to empty object', async () => {
const codeMap = new Map<string, string>();
codeMap.set('TSTypeAliasDeclaration', `type A = { a: string };`);
codeMap.set('TSInterfaceDeclaration', `interface A { a: string; }`);
codeMap.set('TSEnumDeclaration', `enum Direction {}`);
codeMap.set('TSDeclareFunction', `declare function foo()`);
for (const [nodeType, code] of codeMap) {
const ast = await parseSourceCode(code, parsersMap.typescript);
const protoMessage = visitNode(ast as TSESTree.Program);
expect(protoMessage.program.body[0].type).toEqual(NODE_TYPE_ENUM.values[`${nodeType}Type`]);
const tSModuleDeclaration = protoMessage.program.body[0][lowerCaseFirstLetter(nodeType)];
expect(tSModuleDeclaration).toEqual({});
checkAstIsProperlySerializedAndDeserialized(ast as TSESTree.Program, protoMessage, 'foo.ts');
}
});
[
{ nodeType: 'TSTypeAliasDeclaration', code: `type A = { a: string };` },
{ nodeType: 'TSInterfaceDeclaration', code: `interface A { a: string; }`},
{ nodeType: 'TSEnumDeclaration', code: `enum Direction {}`},
{ nodeType: 'TSDeclareFunction', code: `declare function foo()`},
].forEach(({ nodeType, code}) => test(`should serialize ${nodeType} to empty object`, async () => {
const ast = await parseSourceCode(code, parsersMap.typescript);
const protoMessage = visitNode(ast as TSESTree.Program);
expect(protoMessage.program.body[0].type).toEqual(NODE_TYPE_ENUM.values[`${nodeType}Type`]);
const tSModuleDeclaration = protoMessage.program.body[0][lowerCaseFirstLetter(nodeType)];
expect(tSModuleDeclaration).toEqual({});
checkAstIsProperlySerializedAndDeserialized(ast as TSESTree.Program, protoMessage, 'foo.ts');
})
});

Pardon if this is invalid js code, I was typing in this textbox freehand.
This way, you get the name of the node if it fails and its an actual unit-test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for the suggestion!

@@ -26,14 +26,14 @@ class ESTreeTest {
@Test
void test() {
Class<?>[] classes = ESTree.class.getDeclaredClasses();
assertThat(classes).hasSize(118);
assertThat(classes).hasSize(125);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢

Comment on lines 965 to 1011
@Test
void should_create_ts_abstract_method_definition() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSAbstractMethodDefinitionType,
ESTree.TSAbstractMethodDefinition.class
);
}

@Test
void should_create_ts_declare_function() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSDeclareFunctionType,
ESTree.TSDeclareFunction.class
);
}

@Test
void should_create_ts_interface_declaration() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSInterfaceDeclarationType,
ESTree.TSInterfaceDeclaration.class
);
}

@Test
void should_create_ts_enum_declaration() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSEnumDeclarationType,
ESTree.TSEnumDeclaration.class
);
}

@Test
void should_create_ts_type_alias_declaration() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSTypeAliasDeclarationType,
ESTree.TSTypeAliasDeclaration.class
);
}

@Test
void should_create_ts_empty_body_function_expression() {
assertNodeTypeIsParsedToExpectedClass(
NodeType.TSEmptyBodyFunctionExpressionType,
ESTree.TSEmptyBodyFunctionExpression.class
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not fluent in Java, but couldn't this be a @parametrizedTest with these test cases provided with a @methodSource? No need naming these tests and they all share a common implementation from line 1013?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

Comment on lines 80 to 118
@Test
void should_parse_exported_ts_module_declaration() {
assertExportedNodeIsParsedToCorrectObjectType(
"export declare module 'foo'",
ESTree.TSModuleDeclaration.class
);
}

@Test
void should_parse_exported_ts_type_alias_declaration() {
assertExportedNodeIsParsedToCorrectObjectType(
"export type A = { a: 42 }",
ESTree.TSTypeAliasDeclaration.class
);
}

@Test
void should_parse_exported_ts_enum_declaration() {
assertExportedNodeIsParsedToCorrectObjectType(
"export enum A {}",
ESTree.TSEnumDeclaration.class
);
}

@Test
void should_parse_exported_ts_interface_declaration() {
assertExportedNodeIsParsedToCorrectObjectType(
"export interface A {}",
ESTree.TSInterfaceDeclaration.class
);
}

@Test
void should_parse_exported_ts_declare_function() {
assertExportedNodeIsParsedToCorrectObjectType(
"export declare function foo()",
ESTree.TSDeclareFunction.class
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as before, could be parametrized to reduce code-bloat.

Copy link

Quality Gate failed Quality Gate failed

Failed conditions
63.6% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube

@roberto-orlandi-sonarsource roberto-orlandi-sonarsource merged commit 1216a05 into master Mar 31, 2025
16 of 17 checks passed
@roberto-orlandi-sonarsource roberto-orlandi-sonarsource deleted the rob/SONARARMOR-860 branch March 31, 2025 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants