From 31529b2489bd673105fa8defadefe5dcbb3c832a Mon Sep 17 00:00:00 2001 From: reduckted Date: Tue, 23 Oct 2018 20:52:26 +1000 Subject: [PATCH] Enabled the no-any rule. (#570) --- src/exportNameRule.ts | 17 ++-- src/functionNameRule.ts | 16 ++-- src/importNameRule.ts | 7 +- src/maxFuncBodyLengthRule.ts | 27 ++++--- src/mochaNoSideEffectCodeRule.ts | 7 +- src/noConstantConditionRule.ts | 5 +- src/noDuplicateParameterNamesRule.ts | 2 +- src/noHttpStringRule.ts | 2 +- src/noMissingVisibilityModifiersRule.ts | 2 +- src/noUnexternalizedStringsRule.ts | 6 +- src/noUnsupportedBrowserCodeRule.ts | 2 +- src/preferArrayLiteralRule.ts | 5 +- src/promiseMustCompleteRule.ts | 2 +- src/reactA11yAnchorsRule.ts | 7 +- src/reactA11yAriaUnsupportedElementsRule.ts | 10 +-- src/reactA11yEventHasRoleRule.ts | 4 +- src/reactA11yNoOnchangeRule.ts | 4 +- src/reactA11yRoleHasRequiredAriaPropsRule.ts | 6 +- src/reactA11yRoleRule.ts | 4 +- src/reactA11yRoleSupportsAriaPropsRule.ts | 4 +- src/reactNoDangerousHtmlRule.ts | 6 +- src/reactThisBindingIssueRule.ts | 10 +-- src/reactUnusedPropsAndStateRule.ts | 19 +++-- .../FixNoRequireImportsFormatterTests.ts | 8 +- src/tests/FixNoVarKeywordFormatterTests.ts | 10 +-- src/tests/FunctionNameRuleTests.ts | 2 +- src/tests/ImportNameRuleTests.ts | 4 +- src/tests/MaxFuncBodyLengthRuleTests.ts | 2 +- src/tests/NoConstantConditionRuleTests.ts | 2 +- src/tests/NoInnerHtmlRuleTests.ts | 2 +- src/tests/ReactA11yAnchorsRuleTests.ts | 4 +- src/tests/ReactThisBindingIssueRuleTests.ts | 4 +- src/tests/TestHelper.ts | 18 ++--- src/tests/TypeGuardTests.ts | 52 ++++++++++++ src/tests/reactA11yImgHasAltRuleTests.ts | 4 +- src/underscoreConsistentInvocationRule.ts | 5 +- src/utils/AstUtils.ts | 80 +++++++------------ src/utils/BannedTermWalker.ts | 11 +-- src/utils/JsxAttribute.ts | 4 +- src/utils/ScopedSymbolTrackingWalker.ts | 5 +- src/utils/TypeGuard.ts | 10 +++ src/utils/attributes/IRole.ts | 2 +- tslint.json | 2 +- 43 files changed, 231 insertions(+), 174 deletions(-) create mode 100644 src/tests/TypeGuardTests.ts diff --git a/src/exportNameRule.ts b/src/exportNameRule.ts index 851f10dbd..0b17078d7 100644 --- a/src/exportNameRule.ts +++ b/src/exportNameRule.ts @@ -64,9 +64,7 @@ export class Rule extends Lint.Rules.AbstractRule { return options.ruleArguments[0]; } if (options instanceof Array) { - return typeof options[0] === 'object' ? - options[0].allow : - options; // MSE version of tslint somehow requires this + return typeof options[0] === 'object' ? options[0].allow : options; } return undefined; } @@ -147,16 +145,19 @@ export class ExportNameWalker extends ErrorTolerantWalker { private validateExportedElements(exportedElements: ts.Statement[]): void { // only validate the exported elements when a single export statement is made if (exportedElements.length === 1) { - if (exportedElements[0].kind === ts.SyntaxKind.ModuleDeclaration || - exportedElements[0].kind === ts.SyntaxKind.ClassDeclaration || - exportedElements[0].kind === ts.SyntaxKind.FunctionDeclaration) { - this.validateExport((exportedElements[0]).name.text, exportedElements[0]); + const element = exportedElements[0]; + if (ts.isModuleDeclaration(element) || + ts.isClassDeclaration(element) || + ts.isFunctionDeclaration(element)) { + if (element.name !== undefined) { + this.validateExport(element.name!.text, exportedElements[0]); + } } else if (exportedElements[0].kind === ts.SyntaxKind.VariableStatement) { const variableStatement: ts.VariableStatement = exportedElements[0]; // ignore comma separated variable lists if (variableStatement.declarationList.declarations.length === 1) { const variableDeclaration: ts.VariableDeclaration = variableStatement.declarationList.declarations[0]; - this.validateExport((variableDeclaration.name).text, variableDeclaration); + this.validateExport(variableDeclaration.name.getText(), variableDeclaration); } } } diff --git a/src/functionNameRule.ts b/src/functionNameRule.ts index 268394f1d..b8c74c608 100644 --- a/src/functionNameRule.ts +++ b/src/functionNameRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {AstUtils} from './utils/AstUtils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; const METHOD_REGEX = 'method-regex'; const PRIVATE_METHOD_REGEX = 'private-method-regex'; @@ -17,14 +18,14 @@ const VALIDATE_PRIVATE_STATICS_AS_EITHER = 'validate-private-statics-as-either'; const VALID_ARGS = [VALIDATE_PRIVATE_STATICS_AS_PRIVATE, VALIDATE_PRIVATE_STATICS_AS_STATIC, VALIDATE_PRIVATE_STATICS_AS_EITHER]; -function parseOptions(ruleArguments: any[]): Options { +function parseOptions(ruleArguments: unknown[]): Options { if (ruleArguments.length === 0) { return { validateStatics: VALIDATE_PRIVATE_STATICS_AS_PRIVATE }; } - const staticsValidateOption: string = ruleArguments[1]; + const staticsValidateOption: string = ruleArguments[1]; if (VALID_ARGS.indexOf(staticsValidateOption) > -1) { return { validateStatics: staticsValidateOption @@ -100,8 +101,8 @@ class FunctionNameRuleWalker extends ErrorTolerantWalker { constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); this.args = parseOptions(options.ruleArguments); - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { this.methodRegex = this.getOptionOrDefault(opt, METHOD_REGEX, this.methodRegex); this.privateMethodRegex = this.getOptionOrDefault(opt, PRIVATE_METHOD_REGEX, this.privateMethodRegex); this.protectedMethodRegex = this.getOptionOrDefault(opt, PROTECTED_METHOD_REGEX, this.protectedMethodRegex); @@ -153,10 +154,11 @@ class FunctionNameRuleWalker extends ErrorTolerantWalker { super.visitFunctionDeclaration(node); } - private getOptionOrDefault(option: any, key: string, defaultValue: RegExp): RegExp { + private getOptionOrDefault(option: {[key: string]: unknown}, key: string, defaultValue: RegExp): RegExp { try { - if (option[key] !== undefined) { - return new RegExp(option[key]); + const value = option[key]; + if (value !== undefined && (typeof value === 'string' || value instanceof RegExp)) { + return new RegExp(value); } } catch (e) { /* tslint:disable:no-console */ diff --git a/src/importNameRule.ts b/src/importNameRule.ts index 089db7b43..d197e0c15 100644 --- a/src/importNameRule.ts +++ b/src/importNameRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {Utils} from './utils/Utils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; /** * Implementation of the import-name rule. @@ -42,10 +43,10 @@ class ImportNameRuleWalker extends ErrorTolerantWalker { private extractOptions(): { [index: string]: string; } { const result : { [index: string]: string; } = {}; - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { Object.keys(opt).forEach((key: string): void => { - const value: any = opt[key]; + const value: unknown = opt[key]; if (typeof value === 'string') { result[key] = value; } diff --git a/src/maxFuncBodyLengthRule.ts b/src/maxFuncBodyLengthRule.ts index 75b2d6110..45ae0b337 100644 --- a/src/maxFuncBodyLengthRule.ts +++ b/src/maxFuncBodyLengthRule.ts @@ -4,6 +4,7 @@ import {AstUtils} from './utils/AstUtils'; import {Utils} from './utils/Utils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; import {forEachTokenWithTrivia} from 'tsutils'; +import { isObject } from './utils/TypeGuard'; /** * Implementation of the max-func-body-length rule. @@ -148,20 +149,20 @@ class MaxFunctionBodyLengthRuleWalker extends Lint.RuleWalker { } private parseOptions () { - this.getOptions().forEach((opt: any) => { + this.getOptions().forEach((opt: unknown) => { if (typeof(opt) === 'number') { this.maxBodyLength = opt; return; } - if (typeof(opt) === 'object') { - this.maxFuncBodyLength = opt[FUNC_BODY_LENGTH]; - this.maxFuncExpressionBodyLength = opt[FUNC_EXPRESSION_BODY_LENGTH]; - this.maxArrowBodyLength = opt[ARROW_BODY_LENGTH]; - this.maxMethodBodyLength = opt[METHOD_BODY_LENGTH]; - this.maxCtorBodyLength = opt[CTOR_BODY_LENGTH]; - this.ignoreComments = opt[IGNORE_COMMENTS]; - const regex: string = opt[IGNORE_PARAMETERS_TO_FUNCTION]; + if (isObject(opt)) { + this.maxFuncBodyLength = opt[FUNC_BODY_LENGTH]; + this.maxFuncExpressionBodyLength = opt[FUNC_EXPRESSION_BODY_LENGTH]; + this.maxArrowBodyLength = opt[ARROW_BODY_LENGTH]; + this.maxMethodBodyLength = opt[METHOD_BODY_LENGTH]; + this.maxCtorBodyLength = opt[CTOR_BODY_LENGTH]; + this.ignoreComments = !!opt[IGNORE_COMMENTS]; + const regex: string = opt[IGNORE_PARAMETERS_TO_FUNCTION]; if (regex) { this.ignoreParametersToFunctionRegex = new RegExp(regex); } @@ -182,10 +183,10 @@ class MaxFunctionBodyLengthRuleWalker extends Lint.RuleWalker { private formatPlaceText (node: ts.FunctionLikeDeclaration) { const funcTypeText = this.getFuncTypeText(node.kind); - if (node.kind === ts.SyntaxKind.MethodDeclaration || - node.kind === ts.SyntaxKind.FunctionDeclaration || - node.kind === ts.SyntaxKind.FunctionExpression) { - return ` in ${ funcTypeText } ${ (node.name || {text: ''}).text }()`; + if (ts.isMethodDeclaration(node) || + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node)) { + return ` in ${ funcTypeText } ${ node.name ? node.name.getText() : '' }()`; } else if (node.kind === ts.SyntaxKind.Constructor) { return ` in class ${ this.currentClassName }`; } diff --git a/src/mochaNoSideEffectCodeRule.ts b/src/mochaNoSideEffectCodeRule.ts index f238f6d68..4751990ea 100644 --- a/src/mochaNoSideEffectCodeRule.ts +++ b/src/mochaNoSideEffectCodeRule.ts @@ -6,6 +6,7 @@ import {ExtendedMetadata} from './utils/ExtendedMetadata'; import {AstUtils} from './utils/AstUtils'; import {MochaUtils} from './utils/MochaUtils'; import {Utils} from './utils/Utils'; +import { isObject } from './utils/TypeGuard'; const FAILURE_STRING: string = 'Mocha test contains dangerous variable initialization. Move to before()/beforeEach(): '; @@ -44,9 +45,9 @@ class MochaNoSideEffectCodeRuleWalker extends ErrorTolerantWalker { } private parseOptions() { - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { - if (opt.ignore !== undefined) { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { + if (opt.ignore !== undefined && (typeof opt.ignore === 'string' || opt.ignore instanceof RegExp)) { this.ignoreRegex = new RegExp(opt.ignore); } } diff --git a/src/noConstantConditionRule.ts b/src/noConstantConditionRule.ts index 06ec4f710..f9ecbcce2 100644 --- a/src/noConstantConditionRule.ts +++ b/src/noConstantConditionRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {AstUtils} from './utils/AstUtils'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; /** * Implementation of the no-constant-condition rule. @@ -43,8 +44,8 @@ class NoConstantConditionRuleWalker extends ErrorTolerantWalker { private extractBoolean(keyName: string): boolean { let result : boolean = true; - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { if (opt[keyName] === false || opt[keyName] === 'false') { result = false; } diff --git a/src/noDuplicateParameterNamesRule.ts b/src/noDuplicateParameterNamesRule.ts index 6b3ccf8af..aa8d0ef9b 100644 --- a/src/noDuplicateParameterNamesRule.ts +++ b/src/noDuplicateParameterNamesRule.ts @@ -60,7 +60,7 @@ class NoDuplicateParameterNamesWalker extends ErrorTolerantWalker { private validateParameterNames(node : ts.SignatureDeclaration) { const seenNames : {[index: string]: boolean} = {}; node.parameters.forEach((parameter : ts.ParameterDeclaration) : void => { - const parameterName : string = (parameter.name).text; // how does one check if the union type is Identifier? + const parameterName : string = parameter.name.getText(); // how does one check if the union type is Identifier? if (parameterName !== undefined) { if (seenNames[parameterName]) { this.addFailureAt( diff --git a/src/noHttpStringRule.ts b/src/noHttpStringRule.ts index d5b8cb69b..abcb97914 100644 --- a/src/noHttpStringRule.ts +++ b/src/noHttpStringRule.ts @@ -74,7 +74,7 @@ class NoHttpStringWalker extends ErrorTolerantWalker { return options.ruleArguments[0]; } if (options instanceof Array) { - return options; // MSE version of tslint somehow requires this + return options; } return undefined; } diff --git a/src/noMissingVisibilityModifiersRule.ts b/src/noMissingVisibilityModifiersRule.ts index 0e61d4ac1..c50342e43 100644 --- a/src/noMissingVisibilityModifiersRule.ts +++ b/src/noMissingVisibilityModifiersRule.ts @@ -48,7 +48,7 @@ class MissingVisibilityModifierWalker extends ErrorTolerantWalker { super.visitMethodDeclaration(node); } - private isMissingVisibilityModifier(node: ts.Node) : boolean { + private isMissingVisibilityModifier(node: ts.Declaration) : boolean { return !(AstUtils.isPrivate(node) || AstUtils.isProtected(node) || AstUtils.isPublic(node)); } diff --git a/src/noUnexternalizedStringsRule.ts b/src/noUnexternalizedStringsRule.ts index 3f41a1725..448ca8d5a 100644 --- a/src/noUnexternalizedStringsRule.ts +++ b/src/noUnexternalizedStringsRule.ts @@ -54,8 +54,8 @@ class NoUnexternalizedStringsRuleWalker extends ErrorTolerantWalker { this.ignores = Object.create(null); /* tslint:enable:no-null-keyword */ - const options: any[] = this.getOptions(); - const first: UnexternalizedStringsOptions = options && options.length > 0 ? options[0] : undefined; + const options: unknown = this.getOptions(); + const first: UnexternalizedStringsOptions = options && Array.isArray(options) && options.length > 0 ? options[0] : undefined; if (first) { if (Array.isArray(first.signatures)) { first.signatures.forEach((signature: string) => this.signatures[signature] = true); @@ -114,7 +114,7 @@ class NoUnexternalizedStringsRuleWalker extends ErrorTolerantWalker { const kind = parent.kind; if (kind === kinds.CallExpression) { const callExpression = parent; - return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) }}; + return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) }}; } else if (kind === kinds.ImportEqualsDeclaration || kind === kinds.ImportDeclaration || kind === kinds.ExportDeclaration) { return { ignoreUsage: true }; } else if (kind === kinds.VariableDeclaration || kind === kinds.FunctionDeclaration || kind === kinds.PropertyDeclaration diff --git a/src/noUnsupportedBrowserCodeRule.ts b/src/noUnsupportedBrowserCodeRule.ts index 0eaeba54b..2e3dcbf38 100644 --- a/src/noUnsupportedBrowserCodeRule.ts +++ b/src/noUnsupportedBrowserCodeRule.ts @@ -89,7 +89,7 @@ class NoUnsupportedBrowserCodeRuleWalker extends Lint.RuleWalker { private parseSupportedBrowsers(): { [key: string]: BrowserVersion } { const result: { [key: string]: BrowserVersion } = {}; - this.getOptions().forEach((option: any) => { + this.getOptions().forEach((option: unknown) => { if (option instanceof Array) { option.forEach((browserString: string) => { const browser = this.parseBrowserString(browserString); diff --git a/src/preferArrayLiteralRule.ts b/src/preferArrayLiteralRule.ts index 620412b3f..53ddf2f2b 100644 --- a/src/preferArrayLiteralRule.ts +++ b/src/preferArrayLiteralRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {AstUtils} from './utils/AstUtils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; /** * Implementation of the prefer-array-literal rule. @@ -39,8 +40,8 @@ class NoGenericArrayWalker extends ErrorTolerantWalker { constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { this.allowTypeParameters = opt['allow-type-parameters'] === true; } }); diff --git a/src/promiseMustCompleteRule.ts b/src/promiseMustCompleteRule.ts index 6c75b2201..b596db1bc 100644 --- a/src/promiseMustCompleteRule.ts +++ b/src/promiseMustCompleteRule.ts @@ -65,7 +65,7 @@ class PromiseAnalyzer extends ErrorTolerantWalker { protected visitNewExpression(node: ts.NewExpression): void { if (this.isPromiseDeclaration(node) && node.arguments !== undefined) { - const functionArgument: ts.FunctionLikeDeclaration = node.arguments[0]; + const functionArgument: ts.FunctionLikeDeclaration = node.arguments[0]; const functionBody = functionArgument.body; if (functionBody !== undefined) { diff --git a/src/reactA11yAnchorsRule.ts b/src/reactA11yAnchorsRule.ts index 7a050700c..5826cdff7 100644 --- a/src/reactA11yAnchorsRule.ts +++ b/src/reactA11yAnchorsRule.ts @@ -10,6 +10,7 @@ import { getStringLiteral, isEmpty } from './utils/JsxAttribute'; +import { isObject } from './utils/TypeGuard'; export const OPTION_IGNORE_CASE: string = 'ignore-case'; export const OPTION_IGNORE_WHITESPACE: string = 'ignore-whitespace'; @@ -88,13 +89,13 @@ class ReactA11yAnchorsRuleWalker extends ErrorTolerantWalker { } private parseOptions(): void { - this.getOptions().forEach((opt: any) => { + this.getOptions().forEach((opt: unknown) => { if (typeof opt === 'string' && opt === OPTION_IGNORE_CASE) { this.ignoreCase = true; } - if (typeof opt === 'object') { - this.ignoreWhitespace = opt[OPTION_IGNORE_WHITESPACE]; + if (isObject(opt)) { + this.ignoreWhitespace = opt[OPTION_IGNORE_WHITESPACE]; } }); } diff --git a/src/reactA11yAriaUnsupportedElementsRule.ts b/src/reactA11yAriaUnsupportedElementsRule.ts index f69640235..e222e6b4c 100644 --- a/src/reactA11yAriaUnsupportedElementsRule.ts +++ b/src/reactA11yAriaUnsupportedElementsRule.ts @@ -10,8 +10,8 @@ import { IDom } from './utils/attributes/IDom'; import { IAria } from './utils/attributes/IAria'; // tslint:disable:no-require-imports no-var-requires -const DOM_SCHEMA: IDom[] = require('./utils/attributes/domSchema.json'); -const ARIA_SCHEMA: IAria[] = require('./utils/attributes/ariaSchema.json'); +const DOM_SCHEMA: { [key: string]: IDom } = require('./utils/attributes/domSchema.json'); +const ARIA_SCHEMA: { [key: string]: IAria } = require('./utils/attributes/ariaSchema.json'); // tslint:enable:no-require-imports no-var-requires export function getFailureString(tagName: string, ariaAttributeNames: string[]): string { @@ -55,12 +55,12 @@ class ReactA11yAriaUnsupportedElementsWalker extends Lint.RuleWalker { private validateOpeningElement(node: ts.JsxOpeningLikeElement): void { const tagName: string = node.tagName.getText(); - if (!(DOM_SCHEMA)[tagName]) { + if (!DOM_SCHEMA[tagName]) { return; } - const supportAria: boolean = (DOM_SCHEMA)[tagName].supportAria !== undefined - ? (DOM_SCHEMA)[tagName].supportAria + const supportAria: boolean = DOM_SCHEMA[tagName].supportAria !== undefined + ? DOM_SCHEMA[tagName].supportAria : false; if (supportAria) { diff --git a/src/reactA11yEventHasRoleRule.ts b/src/reactA11yEventHasRoleRule.ts index 86a8e35ec..f8868db4e 100644 --- a/src/reactA11yEventHasRoleRule.ts +++ b/src/reactA11yEventHasRoleRule.ts @@ -9,7 +9,7 @@ import { ExtendedMetadata } from './utils/ExtendedMetadata'; import { IDom } from './utils/attributes/IDom'; // tslint:disable-next-line:no-require-imports no-var-requires -const DOM_SCHEMA: IDom[] = require('./utils/attributes/domSchema.json'); +const DOM_SCHEMA: { [key: string]: IDom } = require('./utils/attributes/domSchema.json'); const FAILURE_STRING: string = 'Elements with event handlers must have role attribute.'; const ROLE_STRING: string = 'role'; const TARGET_EVENTS: string[] = ['click', 'keyup', 'keydown', 'keypress', 'mousedown', 'mouseup', @@ -52,7 +52,7 @@ class ReactA11yEventHasRoleWalker extends Lint.RuleWalker { private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement): void { const tagName: string = node.tagName.getText(); - if (!(DOM_SCHEMA)[tagName]) { + if (!DOM_SCHEMA[tagName]) { return; } diff --git a/src/reactA11yNoOnchangeRule.ts b/src/reactA11yNoOnchangeRule.ts index 838d2ba0b..6b9a58837 100644 --- a/src/reactA11yNoOnchangeRule.ts +++ b/src/reactA11yNoOnchangeRule.ts @@ -45,9 +45,9 @@ class ReactA11yNoOnchangeRuleWalker extends ErrorTolerantWalker { private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement) { const tagName: string = node.tagName.getText(); - const options: any[] = this.getOptions(); + const options: unknown = this.getOptions(); - const additionalTagNames: string[] = options.length > 0 ? options[0] : []; + const additionalTagNames: string[] = Array.isArray(options) && options.length > 0 ? options[0] : []; const targetTagNames: string[] = ['select', ...additionalTagNames]; diff --git a/src/reactA11yRoleHasRequiredAriaPropsRule.ts b/src/reactA11yRoleHasRequiredAriaPropsRule.ts index 07c1a149c..37c155cb8 100644 --- a/src/reactA11yRoleHasRequiredAriaPropsRule.ts +++ b/src/reactA11yRoleHasRequiredAriaPropsRule.ts @@ -16,7 +16,7 @@ import { IAria } from './utils/attributes/IAria'; // tslint:disable-next-line:no-require-imports no-var-requires const ROLES_SCHEMA: IRoleSchema = require('./utils/attributes/roleSchema.json'); -const ROLES: IRole[] = ROLES_SCHEMA.roles; +const ROLES: { [key: string]: IRole } = ROLES_SCHEMA.roles; // tslint:disable-next-line:no-require-imports no-var-requires const ARIA_ATTRIBUTES: { [attributeName: string]: IAria } = require('./utils/attributes/ariaSchema.json'); @@ -82,7 +82,7 @@ class A11yRoleHasRequiredAriaPropsWalker extends Lint.RuleWalker { const roleValue = roleProp ? getStringLiteral(roleProp) : getImplicitRole(node); const isImplicitRole: boolean = !roleProp && !!roleValue; const normalizedRoles: string[] = (roleValue || '').toLowerCase().split(' ') - .filter((role: string) => !!(ROLES)[role]); + .filter((role: string) => !!ROLES[role]); if (normalizedRoles.length === 0) { return; @@ -91,7 +91,7 @@ class A11yRoleHasRequiredAriaPropsWalker extends Lint.RuleWalker { let requiredAttributeNames: string[] = []; normalizedRoles.forEach((role: string) => { - requiredAttributeNames = requiredAttributeNames.concat((ROLES)[role].requiredProps || []); + requiredAttributeNames = requiredAttributeNames.concat(ROLES[role].requiredProps || []); }); const attributeNamesInElement: string[] = Object.keys(attributesInElement) diff --git a/src/reactA11yRoleRule.ts b/src/reactA11yRoleRule.ts index 4b8b42db5..d0003f3c3 100644 --- a/src/reactA11yRoleRule.ts +++ b/src/reactA11yRoleRule.ts @@ -11,10 +11,10 @@ import { IRole, IRoleSchema } from './utils/attributes/IRole'; // tslint:disable-next-line:no-require-imports no-var-requires const ROLE_SCHEMA: IRoleSchema = require('./utils/attributes/roleSchema.json'); -const ROLES: IRole[] = ROLE_SCHEMA.roles; +const ROLES: { [key: string]: IRole } = ROLE_SCHEMA.roles; // The array of non-abstract valid rules. -const VALID_ROLES: string[] = Object.keys(ROLES).filter(role => (ROLES)[role].isAbstract === false); +const VALID_ROLES: string[] = Object.keys(ROLES).filter(role => ROLES[role].isAbstract === false); export function getFailureStringUndefinedRole(): string { return '\'role\' attribute empty. Either select a role from https://www.w3.org/TR/wai-aria/roles#role_definitions, ' + diff --git a/src/reactA11yRoleSupportsAriaPropsRule.ts b/src/reactA11yRoleSupportsAriaPropsRule.ts index 36a188fa0..e5c16dec3 100644 --- a/src/reactA11yRoleSupportsAriaPropsRule.ts +++ b/src/reactA11yRoleSupportsAriaPropsRule.ts @@ -16,7 +16,7 @@ const ROLE_SCHEMA: IRoleSchema = require('./utils/attributes/roleSchema.json'); const ARIA_ATTRIBUTES: { [attributeName: string]: IAria } = require('./utils/attributes/ariaSchema.json'); // tslint:enable:no-require-imports no-var-requires -const ROLES: IRole[] = ROLE_SCHEMA.roles; +const ROLES: { [key: string]: IRole } = ROLE_SCHEMA.roles; const ROLE_STRING: string = 'role'; export function getFailureStringForNotImplicitRole(roleNamesInElement: string[], invalidPropNames: string[]): string { @@ -94,7 +94,7 @@ class A11yRoleSupportsAriaPropsWalker extends Lint.RuleWalker { let supportedAttributeNames: string[] = ROLE_SCHEMA.globalSupportedProps; normalizedRoles.forEach((role) => { - supportedAttributeNames = supportedAttributeNames.concat((ROLES)[role].additionalSupportedProps || []); + supportedAttributeNames = supportedAttributeNames.concat(ROLES[role].additionalSupportedProps || []); }); const attributeNamesInElement: string[] = Object.keys(attributesInElement) diff --git a/src/reactNoDangerousHtmlRule.ts b/src/reactNoDangerousHtmlRule.ts index bb9bb40f3..82abbc84c 100644 --- a/src/reactNoDangerousHtmlRule.ts +++ b/src/reactNoDangerousHtmlRule.ts @@ -45,7 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule { return options.ruleArguments[0]; } if (options instanceof Array) { - return options; // MSE version of tslint somehow requires this + return options; } return undefined; } @@ -60,7 +60,7 @@ class NoDangerousHtmlWalker extends ErrorTolerantWalker { } protected visitMethodDeclaration(node: ts.MethodDeclaration): void { - this.currentMethodName = (node.name).text; + this.currentMethodName = node.name.getText(); super.visitMethodDeclaration(node); this.currentMethodName = ''; } @@ -70,7 +70,7 @@ class NoDangerousHtmlWalker extends ErrorTolerantWalker { const keyNode : ts.DeclarationName = node.name; if (keyNode.kind === ts.SyntaxKind.Identifier) { - if ((keyNode).text === 'dangerouslySetInnerHTML') { + if (keyNode.text === 'dangerouslySetInnerHTML') { this.addFailureIfNotSuppressed(node, keyNode); } } diff --git a/src/reactThisBindingIssueRule.ts b/src/reactThisBindingIssueRule.ts index 109b1db9a..4756ec95a 100644 --- a/src/reactThisBindingIssueRule.ts +++ b/src/reactThisBindingIssueRule.ts @@ -6,13 +6,13 @@ import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {Scope} from './utils/Scope'; import {Utils} from './utils/Utils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; const FAILURE_ANONYMOUS_LISTENER: string = 'A new instance of an anonymous method is passed as a JSX attribute: '; const FAILURE_DOUBLE_BIND: string = 'A function is having its \'this\' reference bound twice in the constructor: '; const FAILURE_UNBOUND_LISTENER: string = 'A class method is passed as a JSX attribute without having the \'this\' ' + 'reference bound: '; -const optionExamples: any = [true, {'bind-decorators': ['autobind']}]; /** * Implementation of the react-this-binding-issue rule. */ @@ -39,7 +39,7 @@ export class Rule extends Lint.Rules.AbstractRule { } } }, - optionExamples, + optionExamples: [true, [true, { 'bind-decorators': ['autobind'] }]], optionsDescription: '', typescriptOnly: true, issueClass: 'Non-SDL', @@ -68,11 +68,11 @@ class ReactThisBindingIssueRuleWalker extends ErrorTolerantWalker { constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { this.allowAnonymousListeners = opt['allow-anonymous-listeners'] === true; if (opt['bind-decorators']) { - const allowedDecorators: any[] = opt['bind-decorators']; + const allowedDecorators: unknown = opt['bind-decorators']; if ( !Array.isArray(allowedDecorators) || allowedDecorators.some(decorator => typeof decorator !== 'string') diff --git a/src/reactUnusedPropsAndStateRule.ts b/src/reactUnusedPropsAndStateRule.ts index a0e5eed0b..411508f87 100644 --- a/src/reactUnusedPropsAndStateRule.ts +++ b/src/reactUnusedPropsAndStateRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {Utils} from './utils/Utils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; const PROPS_REGEX = 'props-interface-regex'; const STATE_REGEX = 'state-interface-regex'; @@ -54,18 +55,19 @@ class ReactUnusedPropsAndStateRuleWalker extends ErrorTolerantWalker { constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { this.propsInterfaceRegex = this.getOptionOrDefault(opt, PROPS_REGEX, this.propsInterfaceRegex); this.stateInterfaceRegex = this.getOptionOrDefault(opt, STATE_REGEX, this.stateInterfaceRegex); } }); } - private getOptionOrDefault(option: any, key: string, defaultValue: RegExp): RegExp { + private getOptionOrDefault(option: { [key: string]: unknown }, key: string, defaultValue: RegExp): RegExp { try { - if (option[key] !== undefined) { - return new RegExp(option[key]); + const value: unknown = option[key]; + if (value !== undefined && typeof value === 'string') { + return new RegExp(value); } } catch (e) { /* tslint:disable:no-console */ @@ -194,8 +196,11 @@ class ReactUnusedPropsAndStateRuleWalker extends ErrorTolerantWalker { private getTypeElementData(node: ts.InterfaceDeclaration): { [index: string]: ts.TypeElement } { const result: { [index: string]: ts.TypeElement } = {}; node.members.forEach((typeElement: ts.TypeElement): void => { - if (typeElement.name !== undefined && (typeElement.name).text !== undefined) { - result[(typeElement.name).text] = typeElement; + if (typeElement.name !== undefined) { + const text = typeElement.name.getText(); + if (text !== undefined) { + result[text] = typeElement; + } } }); return result; diff --git a/src/tests/FixNoRequireImportsFormatterTests.ts b/src/tests/FixNoRequireImportsFormatterTests.ts index 2af61b324..a43355153 100644 --- a/src/tests/FixNoRequireImportsFormatterTests.ts +++ b/src/tests/FixNoRequireImportsFormatterTests.ts @@ -40,7 +40,7 @@ import TestHelper = require('./TestHelper'); `; const formatter = new FormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput().trim()).to.equal( ` import {BaseFormatter} from './utils/BaseFormatter'; @@ -53,7 +53,7 @@ import {TestHelper} from './TestHelper'; `; const formatter = new FormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput().trim()).to.equal( `import {TestHelper} from './TestHelper'; `.trim()); @@ -65,7 +65,7 @@ import {TestHelper} from './TestHelper'; console.log(TestHelper);`; const formatter = new FormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput().trim()).to.equal( `import {TestHelper} from './TestHelper'; @@ -80,7 +80,7 @@ import TestHelper = require( `; const formatter = new FormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput().trim()).to.equal( ` import {TestHelper} from diff --git a/src/tests/FixNoVarKeywordFormatterTests.ts b/src/tests/FixNoVarKeywordFormatterTests.ts index 810b0a850..14c43d0d5 100644 --- a/src/tests/FixNoVarKeywordFormatterTests.ts +++ b/src/tests/FixNoVarKeywordFormatterTests.ts @@ -39,7 +39,7 @@ var foo = bar; `; const formatter = new FixNoVarKeywordFormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput()).to.equal( ` let foo = bar; @@ -51,7 +51,7 @@ let foo = bar; `; const formatter = new FixNoVarKeywordFormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput()).to.equal( `let foo = bar; `); @@ -65,7 +65,7 @@ var foo = bar; `; const formatter = new FixNoVarKeywordFormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput()).to.equal( ` @@ -78,7 +78,7 @@ let foo = bar; const input : string = `\r\nvar foo = bar;\r\n`; const formatter = new FixNoVarKeywordFormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput()).to.equal(`\r\nlet foo = bar;\r\n`); }); @@ -86,7 +86,7 @@ let foo = bar; const input : string = `\r\n var foo = bar;\r\n`; const formatter = new FixNoVarKeywordFormatterForTesting(input); - formatter.format(TestHelper.runRule(ruleName, null, input).failures); + formatter.format(TestHelper.runRule(ruleName, undefined, input).failures); chai.expect(formatter.getOutput()).to.equal(`\r\n let foo = bar;\r\n`); }); }); diff --git a/src/tests/FunctionNameRuleTests.ts b/src/tests/FunctionNameRuleTests.ts index 64c6c933f..8f238730a 100644 --- a/src/tests/FunctionNameRuleTests.ts +++ b/src/tests/FunctionNameRuleTests.ts @@ -324,7 +324,7 @@ describe('functionNameRule', () : void => { describe('reading options', (): void => { - let options: any[]; + let options: [boolean, object]; beforeEach((): void => { options = [ true, diff --git a/src/tests/ImportNameRuleTests.ts b/src/tests/ImportNameRuleTests.ts index 793861d2f..8d91be397 100644 --- a/src/tests/ImportNameRuleTests.ts +++ b/src/tests/ImportNameRuleTests.ts @@ -146,7 +146,7 @@ describe('importNameRule', () : void => { import baseChartOptions = require('common/component/chart/options/BaseChartOptions'); `; - const options: any[] = [ true, { + const options = [ true, { 'backbone': 'Backbone', 'react': 'React', 'is-plain-object': 'isPlainObject', @@ -161,7 +161,7 @@ describe('importNameRule', () : void => { import abc from 'abc-tag', import pqr from 'my-module' `; - const options: any[] = [ true, { + const options = [ true, { 'fs/package-name': 'pkg', 'abc-tag': 'abc', 'myModule': 'pqr' diff --git a/src/tests/MaxFuncBodyLengthRuleTests.ts b/src/tests/MaxFuncBodyLengthRuleTests.ts index ec6cfdde7..1d6e66052 100644 --- a/src/tests/MaxFuncBodyLengthRuleTests.ts +++ b/src/tests/MaxFuncBodyLengthRuleTests.ts @@ -5,7 +5,7 @@ import { TestHelper } from './TestHelper'; * Unit tests. */ describe('maxFuncBodyLengthRule', (): void => { - let options: any; + let options: [boolean, number | object, object?]; const script: string = ` // https://github.com/Microsoft/tslint-microsoft-contrib/issues/468#issuecomment-407456317 diff --git a/src/tests/NoConstantConditionRuleTests.ts b/src/tests/NoConstantConditionRuleTests.ts index 1d7064691..71a4ea26d 100644 --- a/src/tests/NoConstantConditionRuleTests.ts +++ b/src/tests/NoConstantConditionRuleTests.ts @@ -35,7 +35,7 @@ describe('noConstantConditionRule', () : void => { doSomething(); } while (true)`; - const options: any[] = [ true, { 'checkLoops': false } ]; + const options = [ true, { 'checkLoops': false } ]; TestHelper.assertViolationsWithOptions(ruleName, options, script, [ ]); }); diff --git a/src/tests/NoInnerHtmlRuleTests.ts b/src/tests/NoInnerHtmlRuleTests.ts index 68df29ff4..84e429c0f 100644 --- a/src/tests/NoInnerHtmlRuleTests.ts +++ b/src/tests/NoInnerHtmlRuleTests.ts @@ -91,7 +91,7 @@ describe('noInnerHtmlRule', () : void => { }); describe('with options', (): void => { - let options: any[]; + let options: [boolean, object]; beforeEach((): void => { options = [ true, diff --git a/src/tests/ReactA11yAnchorsRuleTests.ts b/src/tests/ReactA11yAnchorsRuleTests.ts index c70dc3965..59ddaad72 100644 --- a/src/tests/ReactA11yAnchorsRuleTests.ts +++ b/src/tests/ReactA11yAnchorsRuleTests.ts @@ -276,7 +276,7 @@ describe('reactA11yAnchorsRule', () : void => { }); it('should pass when identical hrefs have texts with different leading/trailing whitespace on ignore-whitespace trim', () : void => { - const opt : any = {}; + const opt : { [key: string]: string } = {}; opt[OPTION_IGNORE_WHITESPACE] = 'trim'; const script : string = ` @@ -291,7 +291,7 @@ describe('reactA11yAnchorsRule', () : void => { }); it('should pass when identical hrefs have texts with different whitespace on ignore-whitespace all', () : void => { - const opt : any = {}; + const opt : { [key: string]: string } = {}; opt[OPTION_IGNORE_WHITESPACE] = 'all'; const script : string = ` diff --git a/src/tests/ReactThisBindingIssueRuleTests.ts b/src/tests/ReactThisBindingIssueRuleTests.ts index 70d1e4513..bfa3b92eb 100644 --- a/src/tests/ReactThisBindingIssueRuleTests.ts +++ b/src/tests/ReactThisBindingIssueRuleTests.ts @@ -132,7 +132,7 @@ describe('reactThisBindingIssueRule', () : void => { it('should pass on anonymous listeners when allow-anonymous-listeners is true', () : void => { - const options: any = [ true, { 'allow-anonymous-listeners': true } ]; + const options = [ true, { 'allow-anonymous-listeners': true } ]; const file : string = 'test-data/ReactThisBinding/ReactThisBindingIssue-anon-instance.tsx'; TestHelper.assertViolationsWithOptions(ruleName, options, file, []); @@ -140,7 +140,7 @@ describe('reactThisBindingIssueRule', () : void => { it('should pass on locally instantiated listeners when allow-anonymous-listeners is true', () : void => { - const options: any = [ true, { 'allow-anonymous-listeners': true } ]; + const options = [ true, { 'allow-anonymous-listeners': true } ]; const file : string = 'test-data/ReactThisBinding/ReactThisBindingIssue-local-instance.tsx'; TestHelper.assertViolationsWithOptions(ruleName, options, file, []); diff --git a/src/tests/TestHelper.ts b/src/tests/TestHelper.ts index 94e4e6a6e..3eb73c76f 100644 --- a/src/tests/TestHelper.ts +++ b/src/tests/TestHelper.ts @@ -54,11 +54,11 @@ export module TestHelper { inputFileOrScript: string, useTypeChecker: boolean = false ) { - runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, [], useTypeChecker); + runRuleAndEnforceAssertions(ruleName, undefined, inputFileOrScript, [], useTypeChecker); } export function assertNoViolationWithOptions( ruleName: string, - options: any[], + options: any[] | undefined, // tslint:disable-line:no-any inputFileOrScript: string, useTypeChecker: boolean = false ) { @@ -66,7 +66,7 @@ export module TestHelper { } export function assertViolationsWithOptions( ruleName: string, - options: any[], + options: any[] | undefined, // tslint:disable-line:no-any inputFileOrScript: string, expectedFailures: ExpectedFailure[], useTypeChecker: boolean = false @@ -79,18 +79,18 @@ export module TestHelper { expectedFailures: ExpectedFailure[], useTypeChecker: boolean = false ) { - runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, expectedFailures, useTypeChecker); + runRuleAndEnforceAssertions(ruleName, undefined, inputFileOrScript, expectedFailures, useTypeChecker); } export function assertViolationsWithTypeChecker( ruleName: string, inputFileOrScript: string, expectedFailures: ExpectedFailure[]) { - runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, expectedFailures, true); + runRuleAndEnforceAssertions(ruleName, undefined, inputFileOrScript, expectedFailures, true); } export function runRule( ruleName: string, - userOptions: string[] | null, + userOptions: any[] | undefined, // tslint:disable-line:no-any inputFileOrScript : string, useTypeChecker : boolean = false ): Lint.LintResult { @@ -102,7 +102,7 @@ export module TestHelper { rulesDirectory: [] }; - if (userOptions != null && userOptions.length > 0) { + if (userOptions !== undefined && userOptions.length > 0) { //options like `[4, 'something', false]` were passed, so prepend `true` to make the array like `[true, 4, 'something', false]` configuration.rules.set(ruleName, { ruleName, @@ -152,7 +152,7 @@ export module TestHelper { function runRuleAndEnforceAssertions( ruleName: string, - userOptions: string[] | null, + userOptions: any[] | undefined, // tslint:disable-line:no-any inputFileOrScript: string, expectedFailures: ExpectedFailure[], useTypeChecker: boolean = false @@ -179,7 +179,7 @@ export module TestHelper { } }); - const errorMessage = `Wrong # of failures: \n${JSON.stringify(actualFailures, null, 2)}`; + const errorMessage = `Wrong # of failures: \n${JSON.stringify(actualFailures, undefined, 2)}`; chai.assert.equal(actualFailures.length, expectedFailures.length, errorMessage); diff --git a/src/tests/TypeGuardTests.ts b/src/tests/TypeGuardTests.ts new file mode 100644 index 000000000..035d8d746 --- /dev/null +++ b/src/tests/TypeGuardTests.ts @@ -0,0 +1,52 @@ +import * as chai from 'chai'; +import * as ts from 'typescript'; +import { isNamed, isObject } from '../utils/TypeGuard'; + +/** + * Unit tests. + */ +describe('TypeGuards', () : void => { + describe('isObject', () : void => { + it('should handle objects.', () : void => { + chai.expect(isObject({ x: 1})).to.equal(true, 'object type is not considered object'); + }); + + it('should handle non-objects.', () : void => { + chai.expect(isObject(true)).to.equal(false, 'boolean type is considered object'); + }); + + it('should handle undefined.', () : void => { + chai.expect(isObject(undefined)).to.equal(false, 'undefined is considered object'); + }); + + it('should handle arrays.', () : void => { + chai.expect(isObject([])).to.equal(false, 'array is considered object'); + }); + }); + + describe('isNamed', () : void => { + it('should return true for nodes with a name.', () : void => { + const node = ts.createClassDeclaration([], [], 'foo', [], [], []); + chai.expect(isNamed(node)).to.equal(true, 'named node is not considered named'); + }); + + it('should return false for nodes with undefined name.', () : void => { + const node = ts.createFunctionDeclaration( + [], + [], + ts.createToken(ts.SyntaxKind.AsteriskToken), + undefined, + [], + [], + undefined, + undefined + ); + chai.expect(isNamed(node)).to.equal(false, 'unnamed node is considered named'); + }); + + it('should return false for nodes without a name.', () : void => { + const node = ts.createArrayLiteral([], false); + chai.expect(isNamed(node)).to.equal(false, 'unnamed node is considered named'); + }); + }); +}); diff --git a/src/tests/reactA11yImgHasAltRuleTests.ts b/src/tests/reactA11yImgHasAltRuleTests.ts index 798687c26..bb338ee84 100644 --- a/src/tests/reactA11yImgHasAltRuleTests.ts +++ b/src/tests/reactA11yImgHasAltRuleTests.ts @@ -55,7 +55,7 @@ describe('reactA11yImgHasAlt', () => { it('when the img element has non-empty alt value and presentation role when option is enabled', () => { const fileName: string = fileDirectory + 'ImgElementHasNonEmptyAltValueAndPresentationRole.tsx'; - const ruleOptions: any[] = [[], { allowNonEmptyAltWithRolePresentation: true }]; + const ruleOptions = [[], { allowNonEmptyAltWithRolePresentation: true }]; TestHelper.assertNoViolationWithOptions(ruleName, ruleOptions, fileName); }); @@ -210,7 +210,7 @@ const c = {''}; }); describe('custom element tests', () => { - const options: any[] = [['Picture']]; // tslint:disable-line:no-any + const options = [['Picture']]; describe('should pass', () => { const fileDirectory: string = 'test-data/a11yImgHasAlt/CustomElementTests/PassingTestInputs/'; diff --git a/src/underscoreConsistentInvocationRule.ts b/src/underscoreConsistentInvocationRule.ts index f0c6ff767..7feb1a285 100644 --- a/src/underscoreConsistentInvocationRule.ts +++ b/src/underscoreConsistentInvocationRule.ts @@ -4,6 +4,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker'; import {AstUtils} from './utils/AstUtils'; import {ExtendedMetadata} from './utils/ExtendedMetadata'; +import { isObject } from './utils/TypeGuard'; const FAILURE_STATIC_FOUND: string = 'Static invocation of underscore function found. Prefer instance version instead: '; const FAILURE_INSTANCE_FOUND: string = 'Underscore instance wrapping of variable found. Prefer underscore static functions instead: '; @@ -57,8 +58,8 @@ class UnderscoreConsistentInvocationRuleWalker extends ErrorTolerantWalker { constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { super(sourceFile, options); - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { if (opt.style === 'static') { this.style = 'static'; } diff --git a/src/utils/AstUtils.ts b/src/utils/AstUtils.ts index d8d503f63..239d03f56 100644 --- a/src/utils/AstUtils.ts +++ b/src/utils/AstUtils.ts @@ -1,4 +1,5 @@ import * as ts from 'typescript'; +import { isNamed } from './TypeGuard'; /** * General utility class. @@ -16,11 +17,13 @@ export module AstUtils { export function getFunctionName(node: ts.CallExpression | ts.NewExpression): string { const expression: ts.Expression = node.expression; - let functionName: string = (expression).text; - if (functionName === undefined && (expression).name) { - functionName = (expression).name.text; + if ('text' in expression) { + return (<{ text: unknown }>expression).text; } - return functionName; + if (isNamed(expression)) { + return expression.name.getText(); + } + return ''; } export function getFunctionTarget(expression: ts.CallExpression): string | undefined { @@ -110,44 +113,24 @@ export module AstUtils { /* tslint:enable:no-console */ } - export function isPrivate(node: ts.Node): boolean { - /* tslint:disable:no-bitwise */ - if ((ts).NodeFlags.Private !== undefined) { - return !!(node.flags & (ts).NodeFlags.Private); - } else { - return !!((ts).getCombinedModifierFlags(node) & (ts).ModifierFlags.Private); - } - /* tslint:enable:no-bitwise */ + export function isPrivate(node: ts.Declaration): boolean { + // tslint:disable-next-line:no-bitwise + return !!(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Private); } - export function isProtected(node: ts.Node): boolean { - /* tslint:disable:no-bitwise */ - if ((ts).NodeFlags.Protected !== undefined) { - return !!(node.flags & (ts).NodeFlags.Protected); - } else { - return !!((ts).getCombinedModifierFlags(node) & (ts).ModifierFlags.Protected); - } - /* tslint:enable:no-bitwise */ + export function isProtected(node: ts.Declaration): boolean { + // tslint:disable-next-line:no-bitwise + return !!(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Protected); } - export function isPublic(node: ts.Node): boolean { - /* tslint:disable:no-bitwise */ - if ((ts).NodeFlags.Public !== undefined) { - return !!(node.flags & (ts).NodeFlags.Public); - } else { - return !!((ts).getCombinedModifierFlags(node) & (ts).ModifierFlags.Public); - } - /* tslint:enable:no-bitwise */ + export function isPublic(node: ts.Declaration): boolean { + // tslint:disable-next-line:no-bitwise + return !!(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Public); } - export function isStatic(node: ts.Node): boolean { - /* tslint:disable:no-bitwise */ - if ((ts).NodeFlags.Static !== undefined) { - return !!(node.flags & (ts).NodeFlags.Static); - } else { - return !!((ts).getCombinedModifierFlags(node) & (ts).ModifierFlags.Static); - } - /* tslint:enable:no-bitwise */ + export function isStatic(node: ts.Declaration): boolean { + // tslint:disable-next-line:no-bitwise + return !!(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static); } export function hasComputedName(node: ts.Node & { name?: ts.PropertyName }): boolean { @@ -202,23 +185,18 @@ export module AstUtils { } export function isExported(node: ts.Node): boolean { - /* tslint:disable:no-bitwise */ - if ((ts).NodeFlags.Export !== undefined) { - return !!(getCombinedNodeFlags(node) & (ts).NodeFlags.Export); - } else { - // typescript 2.1.4 introduces a new edge case for when - // top level variables are exported from a source file - if (node.kind === ts.SyntaxKind.VariableDeclaration - && node.parent.kind === ts.SyntaxKind.VariableDeclarationList - && node.parent.parent.kind === ts.SyntaxKind.VariableStatement) { - if (node.parent.parent.modifiers !== undefined - && AstUtils.hasModifier(node.parent.parent.modifiers, ts.SyntaxKind.ExportKeyword)) { - return true; - } + // typescript 2.1.4 introduces a new edge case for when + // top level variables are exported from a source file + if (node.kind === ts.SyntaxKind.VariableDeclaration + && node.parent.kind === ts.SyntaxKind.VariableDeclarationList + && node.parent.parent.kind === ts.SyntaxKind.VariableStatement) { + if (node.parent.parent.modifiers !== undefined + && AstUtils.hasModifier(node.parent.parent.modifiers, ts.SyntaxKind.ExportKeyword)) { + return true; } - return !!(getCombinedNodeFlags(node) & ts.NodeFlags.ExportContext); } - /* tslint:enable:no-bitwise */ + // tslint:disable-next-line:no-bitwise + return !!(getCombinedNodeFlags(node) & ts.NodeFlags.ExportContext); } export function isAssignmentOperator(token: ts.SyntaxKind): boolean { diff --git a/src/utils/BannedTermWalker.ts b/src/utils/BannedTermWalker.ts index 3761f82c8..05c3ff394 100644 --- a/src/utils/BannedTermWalker.ts +++ b/src/utils/BannedTermWalker.ts @@ -1,6 +1,7 @@ import * as ts from 'typescript'; import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './ErrorTolerantWalker'; +import { isObject, isNamed } from './TypeGuard'; /** * Implementation of the banned-term rulesets. @@ -14,8 +15,8 @@ export class BannedTermWalker extends ErrorTolerantWalker { super(sourceFile, options); this.failureString = failureString; this.bannedTerms = bannedTerms; - this.getOptions().forEach((opt: any) => { - if (typeof(opt) === 'object') { + this.getOptions().forEach((opt: unknown) => { + if (isObject(opt)) { this.allowQuotedProperties = opt['allow-quoted-properties'] === true; } }); @@ -74,9 +75,9 @@ export class BannedTermWalker extends ErrorTolerantWalker { } private validateNode(node: ts.Node) : void { - if ((node).name) { - if ((node).name.text) { - const text : string = (node).name.text; + if (isNamed(node)) { + const text: string = node.name.getText(); + if (text !== undefined) { if (this.isBannedTerm(text)) { this.addFailureAt(node.getStart(), node.getWidth(), this.failureString + text); } diff --git a/src/utils/JsxAttribute.ts b/src/utils/JsxAttribute.ts index 8cbc2717c..65a5807d2 100644 --- a/src/utils/JsxAttribute.ts +++ b/src/utils/JsxAttribute.ts @@ -112,8 +112,8 @@ export function isEmpty(node: ts.JsxAttribute): boolean { return true; } else if (isStringLiteral(initializer)) { return initializer.text.trim() === ''; - } else if ((initializer).expression !== undefined) { - const expression: ts.Expression = (initializer).expression; + } else if (initializer.expression !== undefined) { + const expression: ts.Expression = initializer.expression; if (expression.kind === ts.SyntaxKind.Identifier) { return expression.getText() === 'undefined'; } else if (expression.kind === ts.SyntaxKind.NullKeyword) { diff --git a/src/utils/ScopedSymbolTrackingWalker.ts b/src/utils/ScopedSymbolTrackingWalker.ts index 14c714955..f8884620b 100644 --- a/src/utils/ScopedSymbolTrackingWalker.ts +++ b/src/utils/ScopedSymbolTrackingWalker.ts @@ -3,6 +3,7 @@ import * as Lint from 'tslint'; import {ErrorTolerantWalker} from './ErrorTolerantWalker'; import {AstUtils} from './AstUtils'; import {Scope} from './Scope'; +import { isNamed } from './TypeGuard'; /** * This exists so that you can try to tell the types of variables @@ -57,9 +58,9 @@ export class ScopedSymbolTrackingWalker extends ErrorTolerantWalker { return false; } - if (expression.kind === ts.SyntaxKind.CallExpression) { + if (ts.isCallExpression(expression)) { // calling Function.bind is a special case that makes tslint throw an exception - if ((expression).expression.name && (expression).expression.name.getText() === 'bind') { + if (isNamed(expression.expression) && expression.expression.name.getText() === 'bind') { return true; // for now assume invoking a function named bind returns a function. Follow up with tslint. } diff --git a/src/utils/TypeGuard.ts b/src/utils/TypeGuard.ts index 11c737e85..4f0ddde53 100644 --- a/src/utils/TypeGuard.ts +++ b/src/utils/TypeGuard.ts @@ -44,9 +44,19 @@ export function isJsxOpeningElement(node: ts.Node): node is ts.JsxOpeningElement export function isTrueKeyword(node: ts.Node): node is ts.LiteralExpression { return node && node.kind === ts.SyntaxKind.TrueKeyword; } + export function isFalseKeyword(node: ts.Node): node is ts.LiteralExpression { return node && node.kind === ts.SyntaxKind.FalseKeyword; } + export function isNullKeyword(node: ts.Node): node is ts.LiteralExpression { return node && node.kind === ts.SyntaxKind.NullKeyword; } + +export function isObject(value: unknown): value is { [key: string]: unknown } { + return (value !== undefined) && (typeof value === 'object') && !Array.isArray(value); +} + +export function isNamed(node: ts.Node): node is (ts.Node & { name: ts.Node }) { + return ('name' in node) && (<{ name: ts.Node }>node).name !== undefined; +} diff --git a/src/utils/attributes/IRole.ts b/src/utils/attributes/IRole.ts index 195fc7c94..2ba1dfc24 100644 --- a/src/utils/attributes/IRole.ts +++ b/src/utils/attributes/IRole.ts @@ -11,6 +11,6 @@ export interface IRole { * Interface of role schema. */ export interface IRoleSchema { - roles: IRole[]; + roles: { [key: string]: IRole }; globalSupportedProps: string[]; } diff --git a/tslint.json b/tslint.json index a16a8cb37..c2461b95f 100644 --- a/tslint.json +++ b/tslint.json @@ -78,7 +78,7 @@ "mocha-unneeded-done": true, "new-parens": true, "no-angle-bracket-type-assertion": false, - "no-any": false, + "no-any": true, "no-arg": true, "no-backbone-get-set-outside-model": true, "no-banned-terms": true,