Skip to content

Commit fb14945

Browse files
gkalpakfilipesilva
authored andcommitted
fix(@schematics/angular): correctly handle adding multi-line strings to @NgModule metadata
Previously, `addSymbolToNgModuleMetadata()` assumed that the added symbol would not span multiple lines. In most cases, the added symbol is a single word, so this assumption was correct. In some cases, however, we might want to add a mutli-line string, such as a static method of an `@NgModule`: ```ts imports: [ SomeModule.staticMethod({ prop1: 'val1', prop2: 'val2' }) ] ``` This commit allows `addSymbolToNgModuleMetadata()` to correctly handle multi-line strings by ensuring that added metadata symbols are always put on a new line (even if the array is empty) and each line in the string is indented as necessary.
1 parent 515f042 commit fb14945

File tree

9 files changed

+26
-23
lines changed

9 files changed

+26
-23
lines changed

packages/schematics/angular/component/index_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ describe('Component Schematic', () => {
145145

146146
const tree = await schematicRunner.runSchematicAsync('component', options, appTree).toPromise();
147147
const appModuleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
148-
expect(appModuleContent).toMatch(/exports: \[FooComponent\]/);
148+
expect(appModuleContent).toMatch(/exports: \[\n(\s*) FooComponent\n\1\]/);
149149
});
150150

151151
it('should set the entry component', async () => {
152152
const options = { ...defaultOptions, entryComponent: true };
153153

154154
const tree = await schematicRunner.runSchematicAsync('component', options, appTree).toPromise();
155155
const appModuleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
156-
expect(appModuleContent).toMatch(/entryComponents: \[FooComponent\]/);
156+
expect(appModuleContent).toMatch(/entryComponents: \[\n(\s*) FooComponent\n\1\]/);
157157
});
158158

159159
it('should import into a specified module', async () => {

packages/schematics/angular/directive/index_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('Directive Schematic', () => {
9393
const tree = await schematicRunner.runSchematicAsync('directive', options, appTree)
9494
.toPromise();
9595
const appModuleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
96-
expect(appModuleContent).toMatch(/exports: \[FooDirective\]/);
96+
expect(appModuleContent).toMatch(/exports: \[\n(\s*) FooDirective\n\1\]/);
9797
});
9898

9999
it('should import into a specified module', async () => {

packages/schematics/angular/library/index_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('Library Schematic', () => {
139139
it('should export the component in the NgModule', async () => {
140140
const tree = await schematicRunner.runSchematicAsync('library', defaultOptions, workspaceTree).toPromise();
141141
const fileContent = getFileContent(tree, '/projects/foo/src/lib/foo.module.ts');
142-
expect(fileContent).toContain('exports: [FooComponent]');
142+
expect(fileContent).toMatch(/exports: \[\n(\s*) FooComponent\n\1\]/);
143143
});
144144

145145
describe(`update package.json`, () => {

packages/schematics/angular/pipe/index_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe('Pipe Schematic', () => {
9999

100100
const tree = await schematicRunner.runSchematicAsync('pipe', options, appTree).toPromise();
101101
const appModuleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
102-
expect(appModuleContent).toMatch(/exports: \[FooPipe\]/);
102+
expect(appModuleContent).toMatch(/exports: \[\n(\s*) FooPipe\n\1\]/);
103103
});
104104

105105
it('should respect the flat flag', async () => {

packages/schematics/angular/utility/ast-utils.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import { tags } from '@angular-devkit/core';
89
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript';
910
import { Change, InsertChange, NoopChange } from './change';
1011

@@ -365,15 +366,16 @@ export function addSymbolToNgModuleMetadata(
365366
let toInsert: string;
366367
if (expr.properties.length == 0) {
367368
position = expr.getEnd() - 1;
368-
toInsert = ` ${metadataField}: [${symbolName}]\n`;
369+
toInsert = `\n ${metadataField}: [\n${tags.indentBy(4)`${symbolName}`}\n ]\n`;
369370
} else {
370371
node = expr.properties[expr.properties.length - 1];
371372
position = node.getEnd();
372373
// Get the indentation of the last element, if any.
373374
const text = node.getFullText(source);
374-
const matches = text.match(/^\r?\n\s*/);
375-
if (matches && matches.length > 0) {
376-
toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;
375+
const matches = text.match(/^(\r?\n)(\s*)/);
376+
if (matches) {
377+
toInsert = `,${matches[0]}${metadataField}: [${matches[1]}` +
378+
`${tags.indentBy(matches[2].length + 2)`${symbolName}`}${matches[0]}]`;
377379
} else {
378380
toInsert = `, ${metadataField}: [${symbolName}]`;
379381
}
@@ -404,8 +406,8 @@ export function addSymbolToNgModuleMetadata(
404406

405407
if (Array.isArray(node)) {
406408
const nodeArray = node as {} as Array<ts.Node>;
407-
const symbolsArray = nodeArray.map(node => node.getText());
408-
if (symbolsArray.includes(symbolName)) {
409+
const symbolsArray = nodeArray.map(node => tags.oneLine`${node.getText()}`);
410+
if (symbolsArray.includes(tags.oneLine`${symbolName}`)) {
409411
return [];
410412
}
411413

@@ -417,12 +419,13 @@ export function addSymbolToNgModuleMetadata(
417419
if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {
418420
// We found the field but it's empty. Insert it just before the `]`.
419421
position--;
420-
toInsert = `${symbolName}`;
422+
toInsert = `\n${tags.indentBy(4)`${symbolName}`}\n `;
421423
} else {
422424
// Get the indentation of the last element, if any.
423425
const text = node.getFullText(source);
424-
if (text.match(/^\r?\n/)) {
425-
toInsert = `,${text.match(/^\r?\n(\r?)\s*/)[0]}${symbolName}`;
426+
const matches = text.match(/^(\r?\n)(\s*)/);
427+
if (matches) {
428+
toInsert = `,${matches[1]}${tags.indentBy(matches[2].length)`${symbolName}`}`;
426429
} else {
427430
toInsert = `, ${symbolName}`;
428431
}

packages/schematics/angular/utility/ast-utils_spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('ast utils', () => {
7070
const changes = addExportToModule(source, modulePath, 'FooComponent', './foo.component');
7171
const output = applyChanges(modulePath, moduleContent, changes);
7272
expect(output).toMatch(/import { FooComponent } from '.\/foo.component';/);
73-
expect(output).toMatch(/exports: \[FooComponent\]/);
73+
expect(output).toMatch(/exports: \[\n(\s*) FooComponent\n\1\]/);
7474
});
7575

7676
it('should add export to module if not indented', () => {
@@ -79,7 +79,7 @@ describe('ast utils', () => {
7979
const changes = addExportToModule(source, modulePath, 'FooComponent', './foo.component');
8080
const output = applyChanges(modulePath, moduleContent, changes);
8181
expect(output).toMatch(/import { FooComponent } from '.\/foo.component';/);
82-
expect(output).toMatch(/exports: \[FooComponent\]/);
82+
expect(output).toMatch(/exports: \[\n(\s*) FooComponent\n\1\]/);
8383
});
8484

8585
it('should add declarations to module if not indented', () => {
@@ -120,7 +120,7 @@ describe('ast utils', () => {
120120
expect(changes).not.toBeNull();
121121

122122
const output = applyChanges(modulePath, moduleContent, changes || []);
123-
expect(output).toMatch(/imports: [\s\S]+,\n\s+HelloWorld\n\s+\]/m);
123+
expect(output).toMatch(/imports: \[[^\]]+,\n(\s*) HelloWorld\n\1\]/);
124124
});
125125

126126
it('should add metadata (comma)', () => {
@@ -145,7 +145,7 @@ describe('ast utils', () => {
145145
expect(changes).not.toBeNull();
146146

147147
const output = applyChanges(modulePath, moduleContent, changes || []);
148-
expect(output).toMatch(/imports: [\s\S]+,\n\s+HelloWorld,\n\s+\]/m);
148+
expect(output).toMatch(/imports: \[[^\]]+,\n(\s*) HelloWorld,\n\1\]/);
149149
});
150150

151151
it('should add metadata (missing)', () => {
@@ -167,7 +167,7 @@ describe('ast utils', () => {
167167
expect(changes).not.toBeNull();
168168

169169
const output = applyChanges(modulePath, moduleContent, changes || []);
170-
expect(output).toMatch(/imports: \[HelloWorld]\r?\n/m);
170+
expect(output).toMatch(/imports: \[\n(\s*) HelloWorld\n\1\]/);
171171
});
172172

173173
it('should add metadata (empty)', () => {
@@ -190,7 +190,7 @@ describe('ast utils', () => {
190190
expect(changes).not.toBeNull();
191191

192192
const output = applyChanges(modulePath, moduleContent, changes || []);
193-
expect(output).toMatch(/imports: \[HelloWorld],\r?\n/m);
193+
expect(output).toMatch(/imports: \[\n(\s*) HelloWorld\n\1\],\n/);
194194
});
195195

196196
it(`should handle NgModule with newline after '@'`, () => {

tests/legacy-cli/e2e/tests/generate/component/component-module-export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function() {
77
const modulePath = join('src', 'app', 'app.module.ts');
88

99
return ng('generate', 'component', 'test-component', '--export')
10-
.then(() => expectFileToMatch(modulePath, 'exports: [TestComponentComponent]'))
10+
.then(() => expectFileToMatch(modulePath, /exports: \[\n(\s*) TestComponentComponent\n\1\]/))
1111

1212
// Try to run the unit tests.
1313
.then(() => ng('test', '--watch=false'));

tests/legacy-cli/e2e/tests/generate/directive/directive-module-export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function() {
77
const modulePath = join('src', 'app', 'app.module.ts');
88

99
return ng('generate', 'directive', 'test-directive', '--export')
10-
.then(() => expectFileToMatch(modulePath, 'exports: [TestDirectiveDirective]'))
10+
.then(() => expectFileToMatch(modulePath, /exports: \[\n(\s*) TestDirectiveDirective\n\1\]/))
1111

1212
// Try to run the unit tests.
1313
.then(() => ng('test', '--watch=false'));

tests/legacy-cli/e2e/tests/generate/pipe/pipe-module-export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default function() {
77
const modulePath = join('src', 'app', 'app.module.ts');
88

99
return ng('generate', 'pipe', 'test-pipe', '--export')
10-
.then(() => expectFileToMatch(modulePath, 'exports: [TestPipePipe]'))
10+
.then(() => expectFileToMatch(modulePath, /exports: \[\n(\s*) TestPipePipe\n\1\]/))
1111

1212
// Try to run the unit tests.
1313
.then(() => ng('test', '--watch=false'));

0 commit comments

Comments
 (0)