-
Notifications
You must be signed in to change notification settings - Fork 188
SONARARMOR-817 Parser should serialize TSModuleDeclaration nodes #5226
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
Conversation
b165599
to
1306b70
Compare
adcda81
to
8a06da2
Compare
There is one issue on Next which, according to the way the file is implemented, I am not sure I should fix. Please let me know if I can accept it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not much to add about the newly added functionality. It follows the same pattern as before.
It would be interesting to revisit how all of this code is generated and maintained. There are many 1000s of repetitive lines that do not contain difficult logic. These comments are outside of the scope of the PR though.
function visitTSModuleBlock(node: TSESTree.TSModuleBlock) { | ||
return { | ||
body: node.body.map(visitNode), | ||
}; | ||
} | ||
|
||
function visitTSModuleDeclaration(node: TSESTree.TSModuleDeclaration) { | ||
return { | ||
id: visitNode(node.id), | ||
body: visitNode(node.body), | ||
kind: node.kind, | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I'm reading and looking through this code, there is just so much redundancy and re-inventing the wheel. This seems like it all could be generated or semi-generated. Do you think at some point you would re-think this implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this could be improved. Do you think it should be us to rethink this implementation? As far as I know, the Web squad owns and is responsible for this part of the code. I may be wrong because I was not part of the effort when this was created. In such a case, we could consider it in the future (but probably not very soon since we have a lot of other priorities at the moment)
const tSModuleDeclaration = protoMessage.program.body[0].tSModuleDeclaration; | ||
expect(tSModuleDeclaration.kind).toEqual('namespace'); | ||
expect(tSModuleDeclaration.id.type).toEqual(NODE_TYPE_ENUM.values['IdentifierType']); | ||
expect(tSModuleDeclaration.id.identifier.name).toEqual('Foo'); | ||
expect(tSModuleDeclaration.body.type).toEqual(NODE_TYPE_ENUM.values['TSModuleBlockType']); | ||
expect(tSModuleDeclaration.body.tSModuleBlock.body[0].type).toEqual( | ||
NODE_TYPE_ENUM.values['VariableDeclarationType'], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this tSModuleDeclaration
? is it a class or a plain object? If its an object and you are asserting many properties, would it be more pleasant to compare it like so? With this setup, it's more visible to the reader of the code of the exact nesting and/or structure. However, as I was typing this out, it did feel a bit longish..
const tSModuleDeclaration = protoMessage.program.body[0].tSModuleDeclaration; | |
expect(tSModuleDeclaration.kind).toEqual('namespace'); | |
expect(tSModuleDeclaration.id.type).toEqual(NODE_TYPE_ENUM.values['IdentifierType']); | |
expect(tSModuleDeclaration.id.identifier.name).toEqual('Foo'); | |
expect(tSModuleDeclaration.body.type).toEqual(NODE_TYPE_ENUM.values['TSModuleBlockType']); | |
expect(tSModuleDeclaration.body.tSModuleBlock.body[0].type).toEqual( | |
NODE_TYPE_ENUM.values['VariableDeclarationType'], | |
); | |
expect(protoMessage.program.body[0].tSModuleDeclaration).toStrictEqual({ // or toEqual or toMatchObject | |
kind: 'namespace', | |
id: { | |
type: NODE_TYPE_ENUM.values['IdentifierType'], | |
identifier: { | |
name: 'Foo', | |
} | |
}, | |
body: { | |
type: NODE_TYPE_ENUM.values['TSModuleBlockType'], | |
tSModuleBlock: { | |
body: [ | |
{ | |
type: NODE_TYPE_ENUM.values['VariableDeclarationType'] | |
} | |
] | |
} | |
} | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I like the idea you suggest, having this comparison would make this test super-verbose because to satisfy the equality, there are a lot of other fields that need to be added to your suggestion (locations
and the well-constructed VariableDeclaration
, which has quite a few nested fields as well, for example).
I think that considering this, the current implementation makes more sense. WDYT?
const serialized = serializeInProtobuf(ast as TSESTree.Program, 'foo.ts'); | ||
const deserializedProtoMessage = deserializeProtobuf(serialized); | ||
compareASTs(protoMessage, deserializedProtoMessage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this the same in the entire test suite? Can this be extracted someplace or at least a single call across all of these tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I extracted those three lines to a function
@Test | ||
void test() { | ||
Class<?>[] classes = ESTree.class.getDeclaredClasses(); | ||
assertThat(classes).hasSize(113); | ||
assertThat(classes).hasSize(116); | ||
|
||
//filter all classes that are interface | ||
var ifaceCount = Arrays.stream(classes).filter(Class::isInterface).count(); | ||
assertThat(ifaceCount).isEqualTo(27); | ||
assertThat(ifaceCount).isEqualTo(28); | ||
|
||
var recordCount = Arrays.stream(classes).filter(Class::isRecord).count(); | ||
assertThat(recordCount).isEqualTo(81); | ||
assertThat(recordCount).isEqualTo(83); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this testing? You add unit tests to verify the business logic and that should be enough.
Should this be removed? It's not adding value. It didn't catch that you are doing anything wrong, just added noise by making you update this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an existing test. I just adapted the result based on the modifications I did.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe lets remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I agree with not seeing value in those UTs, I would not make removing them part of this PR. I don't feel comfortable removing tests that I did not write, and that I don't know why they were added in the first place.
@Test | ||
void test_node_subclasses() { | ||
Class<?> sealedClass = ESTree.Node.class; | ||
Class<?>[] permittedSubclasses = sealedClass.getPermittedSubclasses(); | ||
assertThat(permittedSubclasses).hasSize(24); | ||
assertThat(permittedSubclasses).hasSize(25); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same question. Perhaps, I'm not understanding something about the value of these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. This test was just adapted.
@Test | ||
void should_create_ts_module_block() { | ||
Node emptyStatementNode = Node.newBuilder() | ||
.setType(NodeType.EmptyStatementType) | ||
.setEmptyStatement(EmptyStatement.newBuilder().build()) | ||
.build(); | ||
TSModuleBlock tsModuleBlock = TSModuleBlock.newBuilder().addBody(emptyStatementNode).build(); | ||
Node protobufNode = Node.newBuilder() | ||
.setType(NodeType.TSModuleBlockType) | ||
.setTSModuleBlock(tsModuleBlock) | ||
.build(); | ||
|
||
ESTree.Node estree = ESTreeFactory.from(protobufNode, ESTree.Node.class); | ||
assertThat(estree).isInstanceOfSatisfying(ESTree.TSModuleBlock.class, block -> { | ||
assertThat(block.body()).hasSize(1); | ||
assertThat(block.body().get(0)).isInstanceOf(ESTree.EmptyStatement.class); | ||
}); | ||
} | ||
|
||
@Test | ||
void should_create_ts_module_declaration() { | ||
TSModuleDeclaration tsModuleDeclaration = TSModuleDeclaration.newBuilder() | ||
.setId(Node.newBuilder().setType(NodeType.IdentifierType).build()) | ||
.setBody(Node.newBuilder().setType(NodeType.TSModuleBlockType).build()) | ||
.setKind("module") | ||
.build(); | ||
|
||
Node protobufNode = Node.newBuilder() | ||
.setType(NodeType.TSModuleDeclarationType) | ||
.setTSModuleDeclaration(tsModuleDeclaration) | ||
.build(); | ||
|
||
ESTree.Node estree = ESTreeFactory.from(protobufNode, ESTree.Node.class); | ||
assertThat(estree).isInstanceOfSatisfying(ESTree.TSModuleDeclaration.class, module -> { | ||
assertThat(module.id()).isInstanceOf(ESTree.Identifier.class); | ||
assertThat(module.body()).isPresent(); | ||
assertThat(module.body().get()).isInstanceOf(ESTree.TSModuleBlock.class); | ||
assertThat(module.kind()).isEqualTo("module"); | ||
}); | ||
} | ||
|
||
@Test | ||
void should_create_ts_module_declaration_without_body() { | ||
TSModuleDeclaration tsModuleDeclaration = TSModuleDeclaration.newBuilder() | ||
.setId(Node.newBuilder().setType(NodeType.IdentifierType).build()) | ||
.setKind("module") | ||
.build(); | ||
|
||
Node protobufNode = Node.newBuilder() | ||
.setType(NodeType.TSModuleDeclarationType) | ||
.setTSModuleDeclaration(tsModuleDeclaration) | ||
.build(); | ||
|
||
ESTree.Node estree = ESTreeFactory.from(protobufNode, ESTree.Node.class); | ||
assertThat(estree).isInstanceOfSatisfying(ESTree.TSModuleDeclaration.class, module -> { | ||
assertThat(module.id()).isInstanceOf(ESTree.Identifier.class); | ||
assertThat(module.body()).isEmpty(); | ||
assertThat(module.kind()).isEqualTo("module"); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow, this is so verbose. Almost make me think that a simpler, more maintainable way would be to have a generic node, with a field: type (for the TS/JS node name) and props (for the map of properties, which can again be primitives or further nodes) - almost like a json.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very verbose indeed. I went along with the existing UTs style. Finding a more maintainable implementation could be nice in the future.
Thanks for the review. As you mentioned, I think your comments are valid but probably out of the scope of this PR (at least the ones regarding bigger refactoring or changing tests not strictly related to this PR). I will merge as soon as the PR is green again, but happy to continue the open discussions. |
45aed56
to
75472b3
Compare
40fccfe
to
00301e5
Compare
|
SONARARMOR-817
Part of