1 | import * as ts from 'typescript';
|
2 | import * as Lint from 'tslint';
|
3 |
|
4 | import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
|
5 | import { ExtendedMetadata } from './utils/ExtendedMetadata';
|
6 |
|
7 | export const FAILURE_STRING: string = 'Flag (boolean) arguments are not allowed: ';
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | export class Rule extends Lint.Rules.AbstractRule {
|
13 | public static metadata: ExtendedMetadata = {
|
14 | ruleName: 'no-flag-args',
|
15 | type: 'maintainability',
|
16 | description:
|
17 | 'Passing a boolean into a function is a truly terrible practice. ' +
|
18 | 'It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing.',
|
19 | options: null,
|
20 | optionsDescription: '',
|
21 | typescriptOnly: true,
|
22 | issueClass: 'Non-SDL',
|
23 | issueType: 'Warning',
|
24 | severity: 'Important',
|
25 | level: 'Opportunity for Excellence',
|
26 | group: 'Correctness',
|
27 | recommendation: 'true,',
|
28 | commonWeaknessEnumeration: '',
|
29 | };
|
30 |
|
31 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
32 | return this.applyWithWalker(new NoFlagArgsRuleWalker(sourceFile, this.getOptions()));
|
33 | }
|
34 | }
|
35 |
|
36 | class NoFlagArgsRuleWalker extends ErrorTolerantWalker {
|
37 | protected visitParameterDeclaration(node: ts.ParameterDeclaration): void {
|
38 | if (this.isBooleanParameter(node) && !this.belongsToSetAssessor(node)) {
|
39 | const failureMessage = this.makeFailureMessage(node, FAILURE_STRING);
|
40 | this.addFailureAt(node.getStart(), node.getWidth(), failureMessage);
|
41 | }
|
42 | super.visitParameterDeclaration(node);
|
43 | }
|
44 |
|
45 | private isBooleanParameter(node: ts.ParameterDeclaration): boolean {
|
46 | const { type } = node;
|
47 | return type && type.kind === ts.SyntaxKind.BooleanKeyword;
|
48 | }
|
49 |
|
50 | private belongsToSetAssessor(node: ts.ParameterDeclaration): boolean {
|
51 | const { parent } = node;
|
52 | return parent && parent.kind === ts.SyntaxKind.SetAccessor;
|
53 | }
|
54 |
|
55 | private makeFailureMessage(node: ts.ParameterDeclaration, failureString: string): string {
|
56 | const paramName = node.name.getText();
|
57 | const pascalCaseParamName = this.toPascalCase(paramName);
|
58 | const functionName: string | undefined = node.parent.name && node.parent.name.getText();
|
59 | const recommendation = functionName
|
60 | ? '\nSplit the function into two, such as ' +
|
61 | `${functionName}When${pascalCaseParamName}` +
|
62 | ' and ' +
|
63 | `${functionName}WhenNot${pascalCaseParamName}.`
|
64 | : '\nSplit the function into two.';
|
65 | return failureString + paramName + recommendation;
|
66 | }
|
67 |
|
68 | private toPascalCase(str: string) {
|
69 | if (typeof str !== 'string' || str.length === 0) {
|
70 | return str;
|
71 | }
|
72 | return str[0].toUpperCase() + str.slice(1);
|
73 | }
|
74 | }
|
75 |
|
\ | No newline at end of file |