Skip to content

Commit a6249b8

Browse files
committed
refactor(schematics): enable strict null checks
* Enables strict null checks for the schematics and fixes a few potential unsafe accesses. * Supports specifying a project for `ng-add`.
1 parent e124418 commit a6249b8

28 files changed

+74
-59
lines changed

src/lib/schematics/address-form/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function(options: Schema): Rule {
3030
*/
3131
function addFormModulesToModule(options: Schema) {
3232
return (host: Tree) => {
33-
const modulePath = findModuleFromOptions(host, options);
33+
const modulePath = findModuleFromOptions(host, options)!;
3434
addModuleImportToModule(host, modulePath, 'MatInputModule', '@angular/material');
3535
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material');
3636
addModuleImportToModule(host, modulePath, 'MatSelectModule', '@angular/material');

src/lib/schematics/dashboard/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function(options: Schema): Rule {
3030
*/
3131
function addNavModulesToModule(options: Schema) {
3232
return (host: Tree) => {
33-
const modulePath = findModuleFromOptions(host, options);
33+
const modulePath = findModuleFromOptions(host, options)!;
3434
addModuleImportToModule(host, modulePath, 'MatGridListModule', '@angular/material');
3535
addModuleImportToModule(host, modulePath, 'MatCardModule', '@angular/material');
3636
addModuleImportToModule(host, modulePath, 'MatMenuModule', '@angular/material');

src/lib/schematics/install/fonts/head-element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function appendElementToHead(host: Tree, project: WorkspaceProject, eleme
3333
throw `Could not find '<head>' element in HTML file: ${indexPath}`;
3434
}
3535

36-
const endTagOffset = headTag.sourceCodeLocation.endTag.startOffset;
36+
const endTagOffset = headTag.sourceCodeLocation!.endTag.startOffset;
3737
const indentationOffset = getChildElementIndentation(headTag);
3838
const insertion = `${' '.repeat(indentationOffset)}${elementHtml}`;
3939

src/lib/schematics/install/fonts/project-index-html.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import {WorkspaceProject} from '@schematics/angular/utility/config';
1111

1212
/** Looks for the index HTML file in the given project and returns its path. */
1313
export function getIndexHtmlPath(project: WorkspaceProject): string {
14-
const buildTarget = project.architect.build.options;
14+
const buildTarget = project.architect!.build.options;
1515

1616
if (buildTarget.index && buildTarget.index.endsWith('index.html')) {
1717
return buildTarget.index;
1818
}
1919

20-
throw new SchematicsException('No index.html file was found.');
20+
throw new SchematicsException('No project "index.html" file could be found.');
2121
}

src/lib/schematics/install/gestures/project-main-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {WorkspaceProject} from '@schematics/angular/utility/config';
1111

1212
/** Looks for the main TypeScript file in the given project and returns its path. */
1313
export function getProjectMainFile(project: WorkspaceProject): string {
14-
const buildTarget = project.architect.build.options;
14+
const buildTarget = project.architect!.build.options;
1515

1616
if (buildTarget.main) {
1717
return buildTarget.main;

src/lib/schematics/install/index.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Tree} from '@angular-devkit/schematics';
22
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
3-
import {getProjectStyleFile} from '@angular/material/schematics/utils/project-style-file';
3+
import {getProjectStyleFile} from '../utils/project-style-file';
44
import {getIndexHtmlPath} from './fonts/project-index-html';
55
import {getProjectFromWorkspace} from '../utils/get-project';
66
import {getFileContent} from '@schematics/angular/utility/test';
@@ -71,10 +71,10 @@ describe('material-install-schematic', () => {
7171
const expectedStylesPath = normalize(`/${project.root}/src/styles.scss`);
7272

7373
const buffer = tree.read(expectedStylesPath);
74-
const src = buffer!.toString();
74+
const themeContent = buffer!.toString();
7575

76-
expect(src.indexOf(`@import '~@angular/material/theming';`)).toBeGreaterThan(-1);
77-
expect(src.indexOf(`$app-primary`)).toBeGreaterThan(-1);
76+
expect(themeContent).toContain(`@import '~@angular/material/theming';`);
77+
expect(themeContent).toContain(`$app-primary: mat-palette(`);
7878
});
7979

8080
it('should create a custom theme file if no SCSS file could be found', () => {
@@ -112,7 +112,7 @@ describe('material-install-schematic', () => {
112112
const workspace = getWorkspace(tree);
113113
const project = getProjectFromWorkspace(workspace);
114114

115-
const defaultStylesPath = getProjectStyleFile(project);
115+
const defaultStylesPath = getProjectStyleFile(project)!;
116116
const htmlContent = tree.read(defaultStylesPath)!.toString();
117117

118118
expect(htmlContent).toContain('html, body { height: 100%; }');

src/lib/schematics/install/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ function addMaterialAppStyles(options: Schema) {
9494
const workspace = getWorkspace(host);
9595
const project = getProjectFromWorkspace(workspace, options.project);
9696
const styleFilePath = getProjectStyleFile(project);
97-
const buffer = host.read(styleFilePath);
97+
const buffer = host.read(styleFilePath!);
9898

99-
if (!buffer) {
99+
if (!styleFilePath || !buffer) {
100100
return console.warn(`Could not find styles file: "${styleFilePath}". Skipping styles ` +
101101
`generation. Please consider manually adding the "Roboto" font and resetting the ` +
102102
`body margin.`);

src/lib/schematics/install/schema.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
"title": "Material Install Options Schema",
55
"type": "object",
66
"properties": {
7+
"project": {
8+
"type": "string",
9+
"description": "The name of the project.",
10+
"$default": {
11+
"$source": "projectName"
12+
}
13+
},
714
"skipPackageJson": {
815
"type": "boolean",
916
"default": false,

src/lib/schematics/install/schema.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
*/
88

99
export interface Schema {
10+
11+
/** Name of the project to target. */
12+
project: string;
13+
1014
/** Whether to skip package.json install. */
1115
skipPackageJson: boolean;
1216

@@ -15,7 +19,4 @@ export interface Schema {
1519

1620
/** Name of pre-built theme to install. */
1721
theme: 'indigo-pink' | 'deeppurple-amber' | 'pink-bluegrey' | 'purple-green' | 'custom';
18-
19-
/** Name of the project to target. */
20-
project?: string;
2122
}

src/lib/schematics/install/theming/theming.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@ function insertCustomTheme(project: WorkspaceProject, projectName: string, host:
4949
const themeContent = createCustomTheme(projectName);
5050

5151
if (!stylesPath) {
52+
if (!project.sourceRoot) {
53+
throw new Error(`Could not find source root for project: "${projectName}". Please make ` +
54+
`sure that the "sourceRoot" property is set in the workspace config.`);
55+
}
56+
5257
// Normalize the path through the devkit utilities because we want to avoid having
5358
// unnecessary path segments and windows backslash delimiters.
5459
const customThemePath = normalize(join(project.sourceRoot, 'custom-theme.scss'));
5560

5661
host.create(customThemePath, themeContent);
57-
addStyleToTarget(project.architect['build'], host, customThemePath, workspace);
62+
addStyleToTarget(project.architect!['build'], host, customThemePath, workspace);
5863
return;
5964
}
6065

src/lib/schematics/nav/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function(options: Schema): Rule {
3030
*/
3131
function addNavModulesToModule(options: Schema) {
3232
return (host: Tree) => {
33-
const modulePath = findModuleFromOptions(host, options);
33+
const modulePath = findModuleFromOptions(host, options)!;
3434
addModuleImportToModule(host, modulePath, 'LayoutModule', '@angular/cdk/layout');
3535
addModuleImportToModule(host, modulePath, 'MatToolbarModule', '@angular/material');
3636
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material');

src/lib/schematics/table/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function(options: Schema): Rule {
3030
*/
3131
function addTableModulesToModule(options: Schema) {
3232
return (host: Tree) => {
33-
const modulePath = findModuleFromOptions(host, options);
33+
const modulePath = findModuleFromOptions(host, options)!;
3434
addModuleImportToModule(host, modulePath, 'MatTableModule', '@angular/material');
3535
addModuleImportToModule(host, modulePath, 'MatPaginatorModule', '@angular/material');
3636
addModuleImportToModule(host, modulePath, 'MatSortModule', '@angular/material');

src/lib/schematics/tree/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function(options: Schema): Rule {
3030
*/
3131
function addTreeModulesToModule(options: Schema) {
3232
return (host: Tree) => {
33-
const modulePath = findModuleFromOptions(host, options);
33+
const modulePath = findModuleFromOptions(host, options)!;
3434
addModuleImportToModule(host, modulePath, 'MatTreeModule', '@angular/material');
3535
addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material');
3636
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material');

src/lib/schematics/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"moduleResolution": "node",
66
"outDir": "../../../dist/schematics",
77
"noEmitOnError": false,
8+
"strictNullChecks": true,
89
"skipDefaultLibCheck": true,
910
"skipLibCheck": true,
1011
"sourceMap": true,

src/lib/schematics/update/material/data/input-names.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,11 @@ export interface MaterialInputNameData {
1515
/** The new name for the @Input(). */
1616
replaceWith: string;
1717
/** Whitelist where this replacement is made. If omitted it is made in all HTML & CSS */
18-
whitelist?: {
18+
whitelist: {
1919
/** Limit to elements with any of these element tags. */
2020
elements?: string[],
2121
/** Limit to elements with any of these attributes. */
2222
attributes?: string[],
23-
/**
24-
* Whether inputs in stylesheets should be updated or not. Note that inputs inside of
25-
* stylesheets usually don't make sense, but if developers use an input as a plain one-time
26-
* attribute, it can be targeted through CSS selectors.
27-
*/
28-
stylesheet?: boolean,
2923
};
3024
}
3125

src/lib/schematics/update/material/data/output-names.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface MaterialOutputNameData {
1515
/** The new name for the @Output(). */
1616
replaceWith: string;
1717
/** Whitelist where this replacement is made. If omitted it is made in all HTML & CSS */
18-
whitelist?: {
18+
whitelist: {
1919
/** Limit to elements with any of these element tags. */
2020
elements?: string[],
2121
/** Limit to elements with any of these attributes. */

src/lib/schematics/update/material/data/property-names.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface MaterialPropertyNameData {
1717
/** Whitelist where this replacement is made. If omitted it is made for all Classes. */
1818
whitelist: {
1919
/** Replace the property only when its type is one of the given Classes. */
20-
classes?: string[];
20+
classes: string[];
2121
};
2222
}
2323

src/lib/schematics/update/material/transform-change-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ export function getChangesForTarget<T>(target: TargetVersion, data: VersionChang
3333
return [];
3434
}
3535

36-
return data[target].reduce((result, changes) => result.concat(changes.changes), []);
36+
return data[target]!.reduce((result, prData) => result.concat(prData.changes), [] as T[]);
3737
}

src/lib/schematics/update/rules/class-inheritance/classInheritanceCheckRule.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class Walker extends ProgramAwareRuleWalker {
4141

4242
visitClassDeclaration(node: ts.ClassDeclaration) {
4343
const baseTypes = determineBaseTypes(node);
44+
const className = node.name ? node.name.text : '{unknown-name}';
4445

4546
if (!baseTypes) {
4647
return;
@@ -50,7 +51,7 @@ export class Walker extends ProgramAwareRuleWalker {
5051
const data = this.propertyNames.get(typeName);
5152

5253
if (data) {
53-
this.addFailureAtNode(node, `Found class "${bold(node.name.text)}" which extends class ` +
54+
this.addFailureAtNode(node, `Found class "${bold(className)}" which extends class ` +
5455
`"${bold(typeName)}". Please note that the base class property ` +
5556
`"${red(data.replace)}" has changed to "${green(data.replaceWith)}". ` +
5657
`You may need to update your class as well`);

src/lib/schematics/update/rules/class-inheritance/classInheritanceMiscRule.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,19 @@ export class Walker extends ProgramAwareRuleWalker {
2525

2626
visitClassDeclaration(node: ts.ClassDeclaration) {
2727
const baseTypes = determineBaseTypes(node);
28+
const className = node.name ? node.name.text : '{unknown-name}';
2829

2930
if (!baseTypes) {
3031
return;
3132
}
3233

3334
if (baseTypes.includes('MatFormFieldControl')) {
3435
const hasFloatLabelMember = node.members
35-
.find(member => member.name && member.name.getText() === 'shouldFloatLabel');
36+
.filter(member => member.name)
37+
.find(member => member.name!.getText() === 'shouldFloatLabel');
3638

3739
if (!hasFloatLabelMember) {
38-
this.addFailureAtNode(node, `Found class "${bold(node.name.text)}" which extends ` +
40+
this.addFailureAtNode(node, `Found class "${bold(className)}" which extends ` +
3941
`"${bold('MatFormFieldControl')}". This class must define ` +
4042
`"${green('shouldLabelFloat')}" which is now a required property.`);
4143
}

src/lib/schematics/update/rules/input-names/inputNamesStylesheetRule.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ export class Walker extends ComponentWalker {
6868
const replacements: {failureMessage: string, replacement: Replacement}[] = [];
6969

7070
this.data.forEach(name => {
71-
if (name.whitelist && !name.whitelist.stylesheet) {
72-
return;
73-
}
74-
7571
const currentSelector = `[${name.replace}]`;
7672
const updatedSelector = `[${name.replaceWith}]`;
7773

src/lib/schematics/update/rules/input-names/inputNamesTemplateRule.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ export class Walker extends ComponentWalker {
5555

5656
this.data.forEach(name => {
5757
const whitelist = name.whitelist;
58-
const relativeOffsets = [];
58+
const relativeOffsets: number[] = [];
5959
const failureMessage = `Found deprecated @Input() "${red(name.replace)}"` +
6060
` which has been renamed to "${green(name.replaceWith)}"`;
6161

62-
if (!whitelist || whitelist.attributes) {
62+
if (whitelist.attributes) {
6363
relativeOffsets.push(
6464
...findInputsOnElementWithAttr(templateContent, name.replace, whitelist.attributes));
6565
}
6666

67-
if (!whitelist || whitelist.elements) {
67+
if (whitelist.elements) {
6868
relativeOffsets.push(
6969
...findInputsOnElementWithTag(templateContent, name.replace, whitelist.elements));
7070
}

src/lib/schematics/update/rules/output-names/outputNamesTemplateRule.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ export class Walker extends ComponentWalker {
5555

5656
this.data.forEach(name => {
5757
const whitelist = name.whitelist;
58-
const relativeOffsets = [];
58+
const relativeOffsets: number[] = [];
5959
const failureMessage = `Found deprecated @Output() "${red(name.replace)}"` +
6060
` which has been renamed to "${green(name.replaceWith)}"`;
6161

62-
if (!whitelist || whitelist.attributes) {
62+
if (whitelist.attributes) {
6363
relativeOffsets.push(
6464
...findOutputsOnElementWithAttr(templateContent, name.replace, whitelist.attributes));
6565
}
6666

67-
if (!whitelist || whitelist.elements) {
67+
if (whitelist.elements) {
6868
relativeOffsets.push(
6969
...findOutputsOnElementWithTag(templateContent, name.replace, whitelist.elements));
7070
}

src/lib/schematics/update/typescript/base-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export function determineBaseTypes(node: ts.ClassDeclaration): string[] | null {
1515
}
1616

1717
return node.heritageClauses
18-
.reduce((types, clause) => types.concat(clause.types), [])
18+
.reduce((types, clause) => types.concat(clause.types), [] as ts.ExpressionWithTypeArguments[])
1919
.map(typeExpression => typeExpression.expression)
2020
.filter(expression => expression && ts.isIdentifier(expression))
21-
.map(identifier => identifier.text);
21+
.map((identifier: ts.Identifier) => identifier.text);
2222
}

src/lib/schematics/utils/ast.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {InsertChange} from '@schematics/angular/utility/change';
1212
import {getWorkspace, WorkspaceProject} from '@schematics/angular/utility/config';
1313
import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module';
1414
import {getAppModulePath} from '@schematics/angular/utility/ng-ast-utils';
15+
import {Schema as ComponentOptions} from '@schematics/angular/component/schema';
1516
import * as ts from 'typescript';
1617

1718

@@ -29,7 +30,7 @@ export function getSourceFile(host: Tree, path: string): ts.SourceFile {
2930
export function addModuleImportToRootModule(host: Tree, moduleName: string, src: string,
3031
project: WorkspaceProject) {
3132

32-
const modulePath = getAppModulePath(host, project.architect.build.options.main);
33+
const modulePath = getAppModulePath(host, project.architect!.build.options.main);
3334
addModuleImportToModule(host, modulePath, moduleName, src);
3435
}
3536

@@ -40,8 +41,9 @@ export function addModuleImportToRootModule(host: Tree, moduleName: string, src:
4041
* @param moduleName name of module to import
4142
* @param src src location to import
4243
*/
43-
export function addModuleImportToModule(
44-
host: Tree, modulePath: string, moduleName: string, src: string) {
44+
export function addModuleImportToModule(host: Tree, modulePath: string, moduleName: string,
45+
src: string) {
46+
4547
const moduleSource = getSourceFile(host, modulePath);
4648

4749
if (!moduleSource) {
@@ -63,8 +65,9 @@ export function addModuleImportToModule(
6365
}
6466

6567
/** Wraps the internal find module from options with undefined path handling */
66-
export function findModuleFromOptions(host: Tree, options: any) {
68+
export function findModuleFromOptions(host: Tree, options: ComponentOptions): string | undefined {
6769
const workspace = getWorkspace(host);
70+
6871
if (!options.project) {
6972
options.project = Object.keys(workspace.projects)[0];
7073
}

0 commit comments

Comments
 (0)