refactor(compiler): replace Comment nodes with leadingComments property (#38811)

Common AST formats such as TS and Babel do not use a separate
node for comments, but instead attach comments to other AST nodes.
Previously this was worked around in TS by creating a `NotEmittedStatement`
AST node to attach the comment to. But Babel does not have this facility,
so it will not be a viable approach for the linker.

This commit refactors the output AST, to remove the `CommentStmt` and
`JSDocCommentStmt` nodes. Instead statements have a collection of
`leadingComments` that are rendered/attached to the final AST nodes
when being translated or printed.

PR Close #38811
This commit is contained in:
Pete Bacon Darwin
2020-09-11 16:43:23 +01:00
committed by Misko Hevery
parent 7fb388f929
commit d795a00137
24 changed files with 427 additions and 363 deletions

View File

@ -141,7 +141,7 @@ describe('ngc transformer command-line', () => {
write('mymodule.ts', `
import {NgModule} from '@angular/core';
import {AClass} from './aclass';
@NgModule({declarations: []})
export class MyModule {
constructor(importedClass: AClass) {}
@ -382,13 +382,13 @@ describe('ngc transformer command-line', () => {
})
export class MyModule {}
`);
expect(contents).toContain('@fileoverview');
expect(contents).toContain('generated by the Angular template compiler');
expect(contents).toContain('@suppress {suspiciousCode');
expect(contents).toContain(
'/**\n * @fileoverview This file was generated by the Angular template compiler. Do not edit.');
expect(contents).toContain('\n * @suppress {suspiciousCode');
});
it('should be merged with existing fileoverview comments', () => {
const contents = compileAndRead(`/** Hello world. */
const contents = compileAndRead(`/**\n * @fileoverview Hello world.\n */
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@ -398,7 +398,7 @@ describe('ngc transformer command-line', () => {
})
export class MyModule {}
`);
expect(contents).toContain('Hello world.');
expect(contents).toContain('\n * @fileoverview Hello world.\n');
});
it('should only pick file comments', () => {

View File

@ -267,48 +267,69 @@ describe('TypeScriptNodeEmitter', () => {
});
describe('comments', () => {
it('should support a preamble', () => {
expect(emitStmt(o.variable('a').toStmt(), Format.Flat, '/* SomePreamble */'))
.toBe('/* SomePreamble */ a;');
});
it('should support a preamble, which is wrapped as a multi-line comment with no trimming or padding',
() => {
expect(emitStmt(o.variable('a').toStmt(), Format.Raw, '*\n * SomePreamble\n '))
.toBe('/**\n * SomePreamble\n */\na;');
});
it('should support singleline comments', () => {
expect(emitStmt(new o.CommentStmt('Simple comment'))).toBe('// Simple comment');
expect(emitStmt(
new o.ReturnStatement(o.literal(1), null, [o.leadingComment(' a\n b', false)]),
Format.Raw))
.toBe('// a\n// b\nreturn 1;');
});
it('should support multiline comments', () => {
expect(emitStmt(new o.CommentStmt('Multiline comment', true)))
.toBe('/* Multiline comment */');
expect(emitStmt(new o.CommentStmt(`Multiline\ncomment`, true), Format.Raw))
.toBe(`/* Multiline\ncomment */`);
expect(emitStmt(
new o.ReturnStatement(
o.literal(1), null, [o.leadingComment('Multiline comment', true)]),
Format.Raw))
.toBe('/* Multiline comment */\nreturn 1;');
expect(emitStmt(
new o.ReturnStatement(
o.literal(1), null, [o.leadingComment(`Multiline\ncomment`, true)]),
Format.Raw))
.toBe(`/* Multiline\ncomment */\nreturn 1;`);
});
describe('JSDoc comments', () => {
it('should be supported', () => {
expect(emitStmt(new o.JSDocCommentStmt([{text: 'Intro comment'}]), Format.Raw))
.toBe(`/**\n * Intro comment\n */`);
expect(emitStmt(
new o.JSDocCommentStmt([{tagName: o.JSDocTagName.Desc, text: 'description'}]),
new o.ReturnStatement(
o.literal(1), null, [o.jsDocComment([{text: 'Intro comment'}])]),
Format.Raw))
.toBe(`/**\n * @desc description\n */`);
.toBe(`/**\n * Intro comment\n */\nreturn 1;`);
expect(emitStmt(
new o.JSDocCommentStmt([
{text: 'Intro comment'},
{tagName: o.JSDocTagName.Desc, text: 'description'},
{tagName: o.JSDocTagName.Id, text: '{number} identifier 123'},
]),
new o.ReturnStatement(
o.literal(1), null,
[o.jsDocComment([{tagName: o.JSDocTagName.Desc, text: 'description'}])]),
Format.Raw))
.toBe(`/**\n * @desc description\n */\nreturn 1;`);
expect(emitStmt(
new o.ReturnStatement(
o.literal(1), null, [o.jsDocComment([
{text: 'Intro comment'},
{tagName: o.JSDocTagName.Desc, text: 'description'},
{tagName: o.JSDocTagName.Id, text: '{number} identifier 123'},
])]),
Format.Raw))
.toBe(
`/**\n * Intro comment\n * @desc description\n * @id {number} identifier 123\n */`);
`/**\n * Intro comment\n * @desc description\n * @id {number} identifier 123\n */\nreturn 1;`);
});
it('should escape @ in the text', () => {
expect(emitStmt(new o.JSDocCommentStmt([{text: 'email@google.com'}]), Format.Raw))
.toBe(`/**\n * email\\@google.com\n */`);
expect(emitStmt(
new o.ReturnStatement(
o.literal(1), null, [o.jsDocComment([{text: 'email@google.com'}])]),
Format.Raw))
.toBe(`/**\n * email\\@google.com\n */\nreturn 1;`);
});
it('should not allow /* and */ in the text', () => {
expect(() => emitStmt(new o.JSDocCommentStmt([{text: 'some text /* */'}]), Format.Raw))
expect(
() => emitStmt(new o.ReturnStatement(
o.literal(1), null, [o.jsDocComment([{text: 'some text /* */'}])])))
.toThrowError(`JSDoc text cannot contain "/*" and "*/"`);
});
});