feat(angular): add an option to add strict type checking (#3465)

* feat(angular): add an option to add strict type checking

Adding support for strict type checking to Angular application and library generate schematics.

E.g.

`nx generate application myapp --strict`
`nx generate lib mylib --strict`

Closes #3383.

Performs the following configuration changes:

- Enables strict mode in TypeScript, as well as other strictness flags recommended by the TypeScript team. Specifically, forceConsistentCasingInFileNames, noImplicitReturns, noFallthroughCasesInSwitch.

- Turns on strict Angular compiler flags strictTemplates and strictInjectionParameters

These match the flags used in the standard CLI strict mode.

* cleanup(misc): updating import path

Co-authored-by: Ashley Hunter <ashley.hunter@hotmail.co.uk>
This commit is contained in:
Ashley Hunter 2020-08-13 00:15:59 +01:00 committed by GitHub
parent 86b4f4e7b8
commit 10911e25c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 233 additions and 0 deletions

View File

@ -136,6 +136,14 @@ Type: `boolean`
Skip creating spec files.
### strict
Default: `false`
Type: `boolean`
Creates an application with stricter type checking and build optimization options.
### style
Default: `css`

View File

@ -142,6 +142,14 @@ Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `false`
Type: `boolean`
Creates a library with stricter type checking and build optimization options.
### style
Default: `css`

View File

@ -136,6 +136,14 @@ Type: `boolean`
Skip creating spec files.
### strict
Default: `false`
Type: `boolean`
Creates an application with stricter type checking and build optimization options.
### style
Default: `css`

View File

@ -142,6 +142,14 @@ Type: `boolean`
Do not update tsconfig.json for development experience.
### strict
Default: `false`
Type: `boolean`
Creates a library with stricter type checking and build optimization options.
### style
Default: `css`

View File

@ -556,4 +556,43 @@ describe('app', () => {
);
});
});
describe('--strict', () => {
it('should enable strict type checking', async () => {
const tree = await runSchematic(
'app',
{ name: 'my-app', strict: true },
appTree
);
// define all the tsconfig files to update
const configFiles = [
'apps/my-app/tsconfig.json',
'apps/my-app-e2e/tsconfig.e2e.json',
];
for (const configFile of configFiles) {
const { compilerOptions, angularCompilerOptions } = JSON.parse(
tree.readContent(configFile)
);
// check that the TypeScript compiler options have been updated
expect(compilerOptions.forceConsistentCasingInFileNames).toBe(true);
expect(compilerOptions.strict).toBe(true);
expect(compilerOptions.noImplicitReturns).toBe(true);
expect(compilerOptions.noFallthroughCasesInSwitch).toBe(true);
// check that the Angular Template options have been updated
expect(angularCompilerOptions.strictInjectionParameters).toBe(true);
expect(angularCompilerOptions.strictTemplates).toBe(true);
}
// check to see if the workspace configuration has been updated to use strict
// mode by default in future applications
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.schematics['@nrwl/angular:application'].strict).toBe(
true
);
});
});
});

View File

@ -680,6 +680,66 @@ function addProxyConfig(options: NormalizedSchema): Rule {
};
}
function enableStrictTypeChecking(schema: Schema): Rule {
return (host) => {
const options = normalizeOptions(host, schema);
// define all the tsconfig files to update
const configFiles = [
`${options.appProjectRoot}/tsconfig.json`,
`${options.e2eProjectRoot}/tsconfig.e2e.json`,
];
const rules: Rule[] = [];
// iterate each config file, if it exists then update it
for (const configFile of configFiles) {
if (!host.exists(configFile)) {
continue;
}
// Update the settings in the tsconfig.app.json to enable strict type checking.
// This matches the settings defined by the Angular CLI https://angular.io/guide/strict-mode
const rule = updateJsonInTree(configFile, (json) => {
// update the TypeScript settings
json.compilerOptions = {
...(json.compilerOptions ?? {}),
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
// update Angular Template Settings
json.angularCompilerOptions = {
...(json.angularCompilerOptions ?? {}),
strictInjectionParameters: true,
strictTemplates: true,
};
return json;
});
rules.push(rule);
}
// set the default so future applications will default to strict mode
// unless the user has previously set this to false by default
const updateAngularWorkspace = updateWorkspace((workspace) => {
workspace.extensions.schematics = workspace.extensions.schematics || {};
workspace.extensions.schematics['@nrwl/angular:application'] =
workspace.extensions.schematics['@nrwl/angular:application'] || {};
workspace.extensions.schematics['@nrwl/angular:application'].strict =
workspace.extensions.schematics['@nrwl/angular:application'].strict ??
options.strict;
});
return chain([...rules, updateAngularWorkspace]);
};
}
export default function (schema: Schema): Rule {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
@ -762,6 +822,7 @@ export default function (schema: Schema): Rule {
})
: noop(),
options.backendProject ? addProxyConfig(options) : noop(),
options.strict ? enableStrictTypeChecking(options) : noop(),
formatFiles(options),
])(host, context);
};

View File

@ -19,4 +19,5 @@ export interface Schema {
unitTestRunner: UnitTestRunner;
e2eTestRunner: E2eTestRunner;
backendProject?: string;
strict?: boolean;
}

View File

@ -120,6 +120,11 @@
"backendProject": {
"type": "string",
"description": "Backend project that provides data to this application. This sets up proxy.config.json."
},
"strict": {
"type": "boolean",
"description": "Creates an application with stricter type checking and build optimization options.",
"default": false
}
},
"required": []

View File

@ -0,0 +1,50 @@
import { NormalizedSchema } from './normalized-schema';
import { chain, Rule } from '@angular-devkit/schematics';
import { updateJsonInTree, updateWorkspace } from '@nrwl/workspace';
/**
* Enable Strict Mode in the library and spec TS Config
* */
export function enableStrictTypeChecking(options: NormalizedSchema): Rule {
return () => chain([updateTsConfig(options), updateAngularWorkspace()]);
}
function updateTsConfig(options: NormalizedSchema): Rule {
return () => {
// Update the settings in the tsconfig.app.json to enable strict type checking.
// This matches the settings defined by the Angular CLI https://angular.io/guide/strict-mode
return updateJsonInTree(`${options.projectRoot}/tsconfig.json`, (json) => {
// update the TypeScript settings
json.compilerOptions = {
...(json.compilerOptions ?? {}),
forceConsistentCasingInFileNames: true,
strict: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
// update Angular Template Settings
json.angularCompilerOptions = {
...(json.angularCompilerOptions ?? {}),
strictInjectionParameters: true,
strictTemplates: true,
};
return json;
});
};
}
function updateAngularWorkspace(): Rule {
// set the default so future libraries will default to strict mode
// unless the user has previously set this to false by default
return updateWorkspace((workspace) => {
workspace.extensions.schematics = workspace.extensions.schematics || {};
workspace.extensions.schematics['@nrwl/angular:library'] =
workspace.extensions.schematics['@nrwl/angular:library'] || {};
workspace.extensions.schematics['@nrwl/angular:library'].strict =
workspace.extensions.schematics['@nrwl/angular:library'].strict ?? true;
});
}

View File

@ -1149,4 +1149,41 @@ describe('lib', () => {
expect.assertions(1);
});
});
describe('--strict', () => {
it('should enable strict type checking', async () => {
const tree = await runSchematic(
'lib',
{
name: 'myLib',
framework: 'angular',
publishable: true,
importPath: '@myorg/lib',
strict: true,
},
appTree
);
const { compilerOptions, angularCompilerOptions } = JSON.parse(
tree.readContent('libs/my-lib/tsconfig.json')
);
// check that the TypeScript compiler options have been updated
expect(compilerOptions.forceConsistentCasingInFileNames).toBe(true);
expect(compilerOptions.strict).toBe(true);
expect(compilerOptions.noImplicitReturns).toBe(true);
expect(compilerOptions.noFallthroughCasesInSwitch).toBe(true);
// check that the Angular Template options have been updated
expect(angularCompilerOptions.strictInjectionParameters).toBe(true);
expect(angularCompilerOptions.strictTemplates).toBe(true);
// check to see if the workspace configuration has been updated to use strict
// mode by default in future applications
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.schematics['@nrwl/angular:library'].strict).toBe(
true
);
});
});
});

View File

@ -21,6 +21,7 @@ import { updateLibPackageNpmScope } from './lib/update-lib-package-npm-scope';
import { updateProject } from './lib/update-project';
import { updateTsConfig } from './lib/update-tsconfig';
import { Schema } from './schema';
import { enableStrictTypeChecking } from './lib/enable-strict-type-checking';
export default function (schema: Schema): Rule {
return (host: Tree): Rule => {
@ -79,6 +80,7 @@ export default function (schema: Schema): Rule {
? updateLibPackageNpmScope(options)
: noop(),
addModule(options),
options.strict ? enableStrictTypeChecking(options) : noop(),
formatFiles(options),
]);
};

View File

@ -22,6 +22,7 @@ export interface Schema {
lazy?: boolean;
parentModule?: string;
tags?: string;
strict?: boolean;
linter: Linter;
unitTestRunner: UnitTestRunner;

View File

@ -114,6 +114,11 @@
"type": "string",
"description": "The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name."
},
"strict": {
"type": "boolean",
"description": "Creates a library with stricter type checking and build optimization options.",
"default": false
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",