diff --git a/src/rules/noUnsafeAnyRule.ts b/src/rules/noUnsafeAnyRule.ts index fe59c376f88..a497011a4cb 100644 --- a/src/rules/noUnsafeAnyRule.ts +++ b/src/rules/noUnsafeAnyRule.ts @@ -15,9 +15,10 @@ * limitations under the License. */ -import { isReassignmentTarget, isSymbolFlagSet, isTokenKind, isTypeFlagSet, isTypeNodeKind } from "tsutils"; +import { isIdentifier, isReassignmentTarget, isSymbolFlagSet, isTokenKind, isTypeFlagSet, isTypeNodeKind } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; +import { isLowerCase } from "../utils"; export class Rule extends Lint.Rules.TypedRule { /* tslint:disable:object-literal-sort-keys */ @@ -387,9 +388,34 @@ function isNodeAny(node: ts.Node, checker: ts.TypeChecker): boolean { return isAny(checker.getDeclaredTypeOfSymbol(symbol)); } } + + // Lowercase JSX elements are assumed to be allowed by design + if (isJsxNativeElement(node)) { + return false; + } + return isAny(checker.getTypeAtLocation(node)); } +const jsxElementTypes = new Set([ + ts.SyntaxKind.JsxClosingElement, + ts.SyntaxKind.JsxOpeningElement, + ts.SyntaxKind.JsxSelfClosingElement, +]); + +function isJsxNativeElement(node: ts.Node): boolean { + if (!isIdentifier(node) || node.parent === undefined) { + return false; + } + + // TypeScript <=2.1 incorrectly parses JSX fragments + if (node.text === "") { + return true; + } + + return jsxElementTypes.has(node.parent.kind) && isLowerCase(node.text[0]); +} + function isStringLike(expr: ts.Expression, checker: ts.TypeChecker): boolean { return isTypeFlagSet(checker.getTypeAtLocation(expr), ts.TypeFlags.StringLike); } diff --git a/test/rules/no-unsafe-any/commonjsModule.ts b/test/rules/no-unsafe-any/default/commonjsModule.ts similarity index 100% rename from test/rules/no-unsafe-any/commonjsModule.ts rename to test/rules/no-unsafe-any/default/commonjsModule.ts diff --git a/test/rules/no-unsafe-any/es6Module.ts b/test/rules/no-unsafe-any/default/es6Module.ts similarity index 100% rename from test/rules/no-unsafe-any/es6Module.ts rename to test/rules/no-unsafe-any/default/es6Module.ts diff --git a/test/rules/no-unsafe-any/test.ts.lint b/test/rules/no-unsafe-any/default/test.ts.lint similarity index 100% rename from test/rules/no-unsafe-any/test.ts.lint rename to test/rules/no-unsafe-any/default/test.ts.lint diff --git a/test/rules/no-unsafe-any/tsconfig.json b/test/rules/no-unsafe-any/default/tsconfig.json similarity index 100% rename from test/rules/no-unsafe-any/tsconfig.json rename to test/rules/no-unsafe-any/default/tsconfig.json diff --git a/test/rules/no-unsafe-any/tslint.json b/test/rules/no-unsafe-any/default/tslint.json similarity index 100% rename from test/rules/no-unsafe-any/tslint.json rename to test/rules/no-unsafe-any/default/tslint.json diff --git a/test/rules/no-unsafe-any/jsx/test.tsx.lint b/test/rules/no-unsafe-any/jsx/test.tsx.lint new file mode 100644 index 00000000000..edd49f1002c --- /dev/null +++ b/test/rules/no-unsafe-any/jsx/test.tsx.lint @@ -0,0 +1,15 @@ +declare const AnyComponent: any; + +const nativeJsxFull = ; +const nativeJsxShort = ; + +const nativeFragmentEmpty = <>; +const nativeFragmentFull = <>; + +const componentJsxFull = ; + ~~~~~~~~~~~~ [0] + ~~~~~~~~~~~~ [0] +const componentJsxShort = ; + ~~~~~~~~~~~~ [0] + +[0]: Unsafe use of expression of type 'any'. diff --git a/test/rules/no-unsafe-any/jsx/tsconfig.json b/test/rules/no-unsafe-any/jsx/tsconfig.json new file mode 100644 index 00000000000..0b75885e6e8 --- /dev/null +++ b/test/rules/no-unsafe-any/jsx/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "jsx": "react", + "module": "commonjs", + "target": "es6", + "experimentalDecorators": true + } +} diff --git a/test/rules/no-unsafe-any/jsx/tslint.json b/test/rules/no-unsafe-any/jsx/tslint.json new file mode 100644 index 00000000000..b44ff572bc8 --- /dev/null +++ b/test/rules/no-unsafe-any/jsx/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-unsafe-any": true + } +}