Skip to content

Commit 043bd38

Browse files
authored
feat(ng-add): add 'prefer-mapped-imports' rule (#235)
* feat: add dependency to @nativescript/tslint-rules * feat(ng-add): add 'prefer-mapped-imports' rule * refactor: import all {N} tslint symbols from the entry point
1 parent 573cebf commit 043bd38

File tree

7 files changed

+174
-67
lines changed

7 files changed

+174
-67
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"dependencies": {
2020
"@angular-devkit/core": "~8.0.0",
2121
"@angular-devkit/schematics": "~8.0.0",
22-
"@nativescript/tslint-rules": "~0.0.1",
22+
"@nativescript/tslint-rules": "~0.0.3",
2323
"@phenomnomnominal/tsquery": "^3.0.0",
2424
"@schematics/angular": "~8.0.0"
2525
},

src/add-ns/index.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { getAngularProjectSettings, AngularProjectSettings } from '../angular-pr
2323
import { Extensions } from '../generate/utils';
2424

2525
import { Schema as ConvertRelativeImportsSchema } from '../convert-relative-imports/schema';
26+
import { getCompilerOptions } from '../ts-utils';
27+
import { getMappedImportsRuleConfig } from '../mapped-imports-rule-utils';
2628

2729
let extensions: Extensions;
2830
let projectSettings: AngularProjectSettings;
@@ -50,6 +52,7 @@ export default function(options: MigrationOptions): Rule {
5052
addNativeScriptProjectId,
5153

5254
modifyWebTsconfig,
55+
modifyTsLintConfig,
5356

5457
options.skipAutoGeneratedComponent ?
5558
noop() :
@@ -241,6 +244,55 @@ const addNativeScriptProjectId = (tree: Tree, context: SchematicContext) => {
241244
tree.overwrite('package.json', JSON.stringify(packageJson, null, 2));
242245
};
243246

247+
const modifyTsLintConfig = (tree: Tree, context: SchematicContext) => {
248+
context.logger.info('Modifying tslint.json');
249+
250+
const tsLintConfigPath = 'tslint.json';
251+
let tsLintConfig;
252+
try {
253+
tsLintConfig = getJsonFile(tree, tsLintConfigPath);
254+
} catch (e) {
255+
context.logger.warn('Failed to update tslint.json.');
256+
context.logger.debug(e.message);
257+
258+
return;
259+
}
260+
261+
tsLintConfig.extends = tsLintConfig.extends || [];
262+
if (typeof tsLintConfig.extends === 'string') {
263+
tsLintConfig.extends = [
264+
tsLintConfig.extends,
265+
];
266+
}
267+
tsLintConfig.extends.push('@nativescript/tslint-rules');
268+
269+
tsLintConfig.rules = tsLintConfig.rules || {};
270+
const ruleConfig = getRuleConfig(tree);
271+
if (!ruleConfig) {
272+
context.logger.warn('Failed to update tslint.json.');
273+
context.logger.debug('Failed to construct tslint rule configuration.');
274+
275+
return;
276+
}
277+
278+
const { name, options } = ruleConfig;
279+
tsLintConfig.rules[name] = options;
280+
281+
tree.overwrite(tsLintConfigPath, JSON.stringify(tsLintConfig, null, 2));
282+
};
283+
284+
const getRuleConfig = (tree: Tree) => {
285+
const tsConfigPath = projectSettings.tsConfig || 'tsconfig.json';
286+
const compilerOptions = getCompilerOptions(tree, tsConfigPath);
287+
if (!compilerOptions) {
288+
return;
289+
}
290+
291+
const ruleConfig = getMappedImportsRuleConfig(compilerOptions);
292+
293+
return ruleConfig;
294+
};
295+
244296
/**
245297
* Add web-specific path mappings and files
246298
*/
@@ -320,6 +372,7 @@ const addDependencies = () => (tree: Tree, context: SchematicContext) => {
320372
const devDepsToAdd = {
321373
'nativescript-dev-webpack': '~1.0.0',
322374
'@nativescript/schematics': '~0.7.0',
375+
'@nativescript/tslint-rules': '~0.0.2',
323376
};
324377
packageJson.devDependencies = {...devDepsToAdd, ...packageJson.devDependencies};
325378

src/add-ns/index_spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ describe('Add {N} schematic', () => {
8484

8585
expect(devDependencies['nativescript-dev-webpack']).toBeDefined();
8686
expect(devDependencies['@nativescript/schematics']).toBeDefined();
87+
expect(devDependencies['@nativescript/tslint-rules']).toBeDefined();
8788
});
8889

8990
it('should add run scripts to the package json', () => {
@@ -167,6 +168,24 @@ describe('Add {N} schematic', () => {
167168
expect(maps).toContain('src/*');
168169
});
169170

171+
it('should modify tslint.json to include rule for using remapped imports', () => {
172+
const tsLintConfigPath = '/tslint.json';
173+
expect(appTree.files).toContain(tsLintConfigPath);
174+
175+
const tsLintConfig = JSON.parse(getFileContent(appTree, tsLintConfigPath));
176+
const { extends: tsLintExtends, rules: tsLintRules } = tsLintConfig;
177+
178+
expect(tsLintExtends).toEqual(jasmine.any(Array));
179+
expect(tsLintExtends).toContain('@nativescript/tslint-rules');
180+
181+
expect(tsLintRules).toEqual(jasmine.any(Object));
182+
expect(Object.keys(tsLintRules)).toContain('prefer-mapped-imports');
183+
const rule = tsLintRules['prefer-mapped-imports'];
184+
const ruleOptions = rule[1];
185+
const actualBaseUrl = ruleOptions['base-url'];
186+
expect(actualBaseUrl).toEqual('./');
187+
});
188+
170189
it('should generate a sample shared component', () => {
171190
const { files } = appTree;
172191
const appRoutingModuleContent = appTree.readContent('/src/app/app-routing.module.tns.ts');

src/convert-relative-imports/index.ts

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { IRule, Replacement } from 'tslint';
33
import { tsquery } from '@phenomnomnominal/tsquery';
44
import { Tree, SchematicContext, isContentAction } from '@angular-devkit/schematics';
55
import { LoggerApi } from '@angular-devkit/core/src/logger';
6-
import { PreferMappedImportsRule } from '@nativescript/tslint-rules';
7-
import { parseCompilerOptions } from '@nativescript/tslint-rules/dist/preferMappedImportsRule';
86

9-
import { parseTsConfigFile } from '../ts-utils';
7+
import { getCompilerOptions } from '../ts-utils';
108
import { getFileContents } from '../utils';
119
import { getTsConfigFromProject } from '../angular-project-parser';
10+
import { getMappedImportsRule } from '../mapped-imports-rule-utils';
11+
1212
import { Schema as ConvertRelativeImportsSchema } from './schema';
1313

1414
// TODO: add link to the docs
@@ -25,7 +25,7 @@ export default function(options: ConvertRelativeImportsSchema) {
2525
return tree;
2626
}
2727

28-
const tsConfigPath = getTsConfigPath(tree, options.project, logger) || 'tsconfig.json';
28+
const tsConfigPath = getTsConfigFromProject(tree, options.project) || 'tsconfig.json';
2929
const compilerOptions = getCompilerOptions(tree, tsConfigPath);
3030

3131
if (!compilerOptions) {
@@ -61,7 +61,7 @@ function fixImports(
6161
filePaths: Set<string>,
6262
compilerOptions: ts.CompilerOptions,
6363
): Tree {
64-
const rule = generateTslintRule(compilerOptions);
64+
const rule = getMappedImportsRule(compilerOptions);
6565
if (!rule) {
6666
logger.debug('Convert Relative Imports: Failed to extract remap options from the TS compiler options.');
6767
logger.error(conversionFailureMessage);
@@ -91,49 +91,3 @@ function applyTslintRuleFixes(rule: IRule, filePath: string, fileContent: string
9191

9292
return fixedContent;
9393
}
94-
95-
function generateTslintRule(compilerOptions: ts.CompilerOptions): PreferMappedImportsRule | undefined {
96-
const remapOptions = parseCompilerOptions(compilerOptions);
97-
if (!remapOptions) {
98-
return;
99-
}
100-
101-
const tslintRuleArguments = {
102-
prefix: remapOptions.prefix,
103-
'prefix-mapped-to': remapOptions.prefixMappedTo,
104-
'base-url': remapOptions.baseUrl,
105-
};
106-
107-
const rule = new PreferMappedImportsRule({
108-
ruleArguments: [tslintRuleArguments],
109-
ruleName: 'prefer-mapped-imports',
110-
ruleSeverity: 'error',
111-
disabledIntervals: [],
112-
});
113-
114-
return rule;
115-
}
116-
117-
function getTsConfigPath(tree: Tree, projectName: string, logger: LoggerApi): string | undefined {
118-
let tsConfig: string | undefined;
119-
try {
120-
tsConfig = getTsConfigFromProject(tree, projectName);
121-
} catch (e) {
122-
logger.error(e);
123-
}
124-
125-
return tsConfig;
126-
}
127-
128-
function getCompilerOptions(tree: Tree, tsConfigPath: string)
129-
: ts.CompilerOptions | undefined {
130-
131-
const tsConfigObject = parseTsConfigFile(tree, tsConfigPath);
132-
if (!tsConfigObject) {
133-
return;
134-
}
135-
136-
const compilerOptions = tsConfigObject.options;
137-
138-
return compilerOptions;
139-
}

src/mapped-imports-rule-utils.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as ts from 'typescript';
2+
import {
3+
PreferMappedImportsRule,
4+
RuleArgs,
5+
parseCompilerOptions,
6+
} from '@nativescript/tslint-rules';
7+
8+
const RULE_NAME = 'prefer-mapped-imports';
9+
10+
export function getMappedImportsRule(compilerOptions: ts.CompilerOptions):
11+
PreferMappedImportsRule | undefined {
12+
13+
const tslintRuleArguments = getMappedImportsArguments(compilerOptions);
14+
if (!tslintRuleArguments) {
15+
return;
16+
}
17+
18+
const rule = new PreferMappedImportsRule({
19+
ruleArguments: [tslintRuleArguments],
20+
ruleName: RULE_NAME,
21+
ruleSeverity: 'error',
22+
disabledIntervals: [],
23+
});
24+
25+
return rule;
26+
}
27+
28+
export interface RuleConfig {
29+
name: string;
30+
options: object;
31+
}
32+
33+
export function getMappedImportsRuleConfig(compilerOptions: ts.CompilerOptions): RuleConfig | undefined {
34+
const tslintRuleArguments = getMappedImportsArguments(compilerOptions);
35+
if (!tslintRuleArguments) {
36+
return;
37+
}
38+
39+
const ruleOptions = [
40+
true,
41+
tslintRuleArguments,
42+
];
43+
44+
return {
45+
name: RULE_NAME,
46+
options: ruleOptions,
47+
};
48+
}
49+
50+
function getMappedImportsArguments(compilerOptions: ts.CompilerOptions)
51+
: RuleArgs | undefined {
52+
53+
const remapOptions = parseCompilerOptions(compilerOptions);
54+
if (!remapOptions) {
55+
return;
56+
}
57+
58+
const { prefix, prefixMappedTo, baseUrl } = remapOptions;
59+
const tslintRuleArguments = {
60+
prefix,
61+
'prefix-mapped-to': prefixMappedTo,
62+
'base-url': baseUrl,
63+
};
64+
65+
return tslintRuleArguments;
66+
}

src/ng-new/shared/_files/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@angular/compiler-cli": "~8.0.1",
4141
"@angular-devkit/build-angular": "~0.800.0",
4242
"@nativescript/schematics": "~0.7.0",
43+
"@nativescript/tslint-rules": "~0.0.2",
4344
"@types/jasmine": "~3.3.8",
4445
"@types/jasminewd2": "~2.0.3",
4546
"@types/node": "~8.9.4",

src/ts-utils.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { dirname } from 'path';
2+
13
import {
24
addImportToModule,
35
addBootstrapToModule,
@@ -9,7 +11,6 @@ import { SchematicsException, Rule, Tree } from '@angular-devkit/schematics';
911
import * as ts from 'typescript';
1012

1113
import { toComponentClassName, Node, removeNode, getFileContents, getJsonFile } from './utils';
12-
import { dirname } from 'path';
1314

1415
class RemoveContent implements Node {
1516
constructor(private pos: number, private end: number) {
@@ -620,7 +621,33 @@ export const replaceTextInNode = (tree: Tree, node: ts.Node, oldText: string, ne
620621
tree.commitUpdate(recorder);
621622
};
622623

623-
export function parseTsConfigFile(tree: Tree, tsConfigPath: string): ts.ParsedCommandLine {
624+
export const getSourceFile = (host: Tree, path: string): ts.SourceFile => {
625+
const buffer = host.read(path);
626+
if (!buffer) {
627+
throw new SchematicsException(
628+
`Could not find file at ${path}. See https://github.com/NativeScript/nativescript-schematics/issues/172.`,
629+
);
630+
}
631+
const content = buffer.toString();
632+
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
633+
634+
return source;
635+
};
636+
637+
export function getCompilerOptions(tree: Tree, tsConfigPath: string)
638+
: ts.CompilerOptions | undefined {
639+
640+
const tsConfigObject = parseTsConfigFile(tree, tsConfigPath);
641+
if (!tsConfigObject) {
642+
return;
643+
}
644+
645+
const compilerOptions = tsConfigObject.options;
646+
647+
return compilerOptions;
648+
}
649+
650+
function parseTsConfigFile(tree: Tree, tsConfigPath: string): ts.ParsedCommandLine {
624651
const config = getJsonFile(tree, tsConfigPath);
625652
const host: ts.ParseConfigHost = {
626653
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
@@ -634,16 +661,3 @@ export function parseTsConfigFile(tree: Tree, tsConfigPath: string): ts.ParsedCo
634661

635662
return tsConfigObject;
636663
}
637-
638-
export const getSourceFile = (host: Tree, path: string): ts.SourceFile => {
639-
const buffer = host.read(path);
640-
if (!buffer) {
641-
throw new SchematicsException(
642-
`Could not find file at ${path}. See https://github.com/NativeScript/nativescript-schematics/issues/172.`,
643-
);
644-
}
645-
const content = buffer.toString();
646-
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
647-
648-
return source;
649-
};

0 commit comments

Comments
 (0)