diff --git a/src/rules/noInferredEmptyObjectTypeRule.ts b/src/rules/noInferredEmptyObjectTypeRule.ts index 9d49f32bace..c87f7ae336f 100644 --- a/src/rules/noInferredEmptyObjectTypeRule.ts +++ b/src/rules/noInferredEmptyObjectTypeRule.ts @@ -15,9 +15,9 @@ * limitations under the License. */ +import { isObjectFlagSet, isObjectType, isTypeReference } from "tsutils"; import * as ts from "typescript"; import * as Lint from "../index"; -import { isObjectFlagSet, isTypeFlagSet } from "../language/utils"; export class Rule extends Lint.Rules.TypedRule { /* tslint:disable:object-literal-sort-keys */ @@ -60,12 +60,10 @@ class NoInferredEmptyObjectTypeRule extends Lint.AbstractWalker { private checkNewExpression(node: ts.NewExpression): void { if (node.typeArguments === undefined) { - const objType = this.checker.getTypeAtLocation(node) as ts.TypeReference; - if (isTypeFlagSet(objType, ts.TypeFlags.Object) && objType.typeArguments !== undefined) { - const typeArgs = objType.typeArguments as ts.ObjectType[]; - if (typeArgs.some((a) => this.isEmptyObjectInterface(a))) { - this.addFailureAtNode(node, Rule.EMPTY_INTERFACE_INSTANCE); - } + const type = this.checker.getTypeAtLocation(node); + if (isTypeReference(type) && type.typeArguments !== undefined && + type.typeArguments.some((a) => isObjectType(a) && this.isEmptyObjectInterface(a))) { + this.addFailureAtNode(node, Rule.EMPTY_INTERFACE_INSTANCE); } } } @@ -80,28 +78,20 @@ class NoInferredEmptyObjectTypeRule extends Lint.AbstractWalker { return; } - const retType = this.checker.getReturnTypeOfSignature(callSig) as ts.TypeReference; - if (this.isEmptyObjectInterface(retType)) { + const retType = this.checker.getReturnTypeOfSignature(callSig); + if (isObjectType(retType) && this.isEmptyObjectInterface(retType)) { this.addFailureAtNode(node, Rule.EMPTY_INTERFACE_FUNCTION); } } private isEmptyObjectInterface(objType: ts.ObjectType): boolean { - const isAnonymous = isObjectFlagSet(objType, ts.ObjectFlags.Anonymous); - let hasProblematicCallSignatures = false; - const hasProperties = (objType.getProperties() !== undefined && objType.getProperties().length > 0); - const hasNumberIndexType = objType.getNumberIndexType() !== undefined; - const hasStringIndexType = objType.getStringIndexType() !== undefined; - const callSig = objType.getCallSignatures(); - if (callSig !== undefined && callSig.length > 0) { - const isClean = callSig.every((sig) => { - const csigRetType = this.checker.getReturnTypeOfSignature(sig) as ts.TypeReference; - return this.isEmptyObjectInterface(csigRetType); + return isObjectFlagSet(objType, ts.ObjectFlags.Anonymous) && + objType.getProperties().length === 0 && + objType.getNumberIndexType() === undefined && + objType.getStringIndexType() === undefined && + objType.getCallSignatures().every((signature) => { + const type = this.checker.getReturnTypeOfSignature(signature); + return isObjectType(type) && this.isEmptyObjectInterface(type); }); - if (!isClean) { - hasProblematicCallSignatures = true; - } - } - return (isAnonymous && !hasProblematicCallSignatures && !hasProperties && !hasNumberIndexType && !hasStringIndexType); } } diff --git a/src/test.ts b/src/test.ts index 2eedbcebd5c..dd3fa4a9896 100644 --- a/src/test.ts +++ b/src/test.ts @@ -27,7 +27,7 @@ import {Replacement} from "./language/rule/rule"; import * as Linter from "./linter"; import {LintError} from "./test/lintError"; import * as parse from "./test/parse"; -import {mapDefined, readBufferWithDetectedEncoding} from "./utils"; +import {denormalizeWinPath, mapDefined, readBufferWithDetectedEncoding} from "./utils"; const MARKUP_FILE_EXTENSION = ".lint"; const FIXES_FILE_EXTENSION = ".fix"; @@ -90,8 +90,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ for (const fileToLint of filesToLint) { const isEncodingRule = path.basename(testDirectory) === "encoding"; - const fileBasename = path.basename(fileToLint, MARKUP_FILE_EXTENSION); - const fileCompileName = fileBasename.replace(/\.lint$/, ""); + const fileCompileName = denormalizeWinPath(path.resolve(fileToLint.replace(/\.lint$/, ""))); let fileText = isEncodingRule ? readBufferWithDetectedEncoding(fs.readFileSync(fileToLint)) : fs.readFileSync(fileToLint, "utf-8"); const tsVersionRequirement = parse.getTypescriptVersionRequirement(fileText); if (tsVersionRequirement !== undefined) { @@ -114,26 +113,24 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ let program: ts.Program | undefined; if (hasConfig) { const compilerHost: ts.CompilerHost = { - fileExists: () => true, - getCanonicalFileName: (filename: string) => filename, - getCurrentDirectory: () => "", + fileExists: (file) => file === fileCompileName || fs.existsSync(file), + getCanonicalFileName: (filename) => filename, + getCurrentDirectory: () => process.cwd(), getDefaultLibFileName: () => ts.getDefaultLibFileName(compilerOptions), - getDirectories: (_path: string) => [], + getDirectories: (path) => fs.readdirSync(path), getNewLine: () => "\n", - getSourceFile(filenameToGet: string) { + getSourceFile(filenameToGet) { const target = compilerOptions.target === undefined ? ts.ScriptTarget.ES5 : compilerOptions.target; if (filenameToGet === ts.getDefaultLibFileName(compilerOptions)) { const fileContent = fs.readFileSync(ts.getDefaultLibFilePath(compilerOptions), "utf8"); return ts.createSourceFile(filenameToGet, fileContent, target); - } else if (filenameToGet === fileCompileName) { - return ts.createSourceFile(fileBasename, fileTextWithoutMarkup, target, true); - } else if (fs.existsSync(path.resolve(path.dirname(fileToLint), filenameToGet))) { - const text = fs.readFileSync(path.resolve(path.dirname(fileToLint), filenameToGet), "utf8"); - return ts.createSourceFile(filenameToGet, text, target, true); + } else if (denormalizeWinPath(filenameToGet) === fileCompileName) { + return ts.createSourceFile(filenameToGet, fileTextWithoutMarkup, target, true); } - throw new Error(`Couldn't get source file '${filenameToGet}'`); + const text = fs.readFileSync(filenameToGet, "utf8"); + return ts.createSourceFile(filenameToGet, text, target, true); }, - readFile: (x: string) => x, + readFile: (x) => x, useCaseSensitiveFileNames: () => true, writeFile: () => null, }; @@ -149,7 +146,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[ }; const linter = new Linter(lintOptions, program); // Need to use the true path (ending in '.lint') for "encoding" rule so that it can read the file. - linter.lint(isEncodingRule ? fileToLint : fileBasename, fileTextWithoutMarkup, tslintConfig); + linter.lint(isEncodingRule ? fileToLint : fileCompileName, fileTextWithoutMarkup, tslintConfig); const failures = linter.getResult().failures; const errorsFromLinter: LintError[] = failures.map((failure) => { const startLineAndCharacter = failure.getStartPosition().getLineAndCharacter(); diff --git a/src/utils.ts b/src/utils.ts index 28de3a994d7..52ebf0a5d89 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -209,3 +209,8 @@ export function detectBufferEncoding(buffer: Buffer, length = buffer.length): En return "utf8"; } + +// converts Windows normalized paths (with backwards slash `\`) to paths used by TypeScript (with forward slash `/`) +export function denormalizeWinPath(path: string): string { + return path.replace(/\\/g, "/"); +} diff --git a/test/executable/executableTests.ts b/test/executable/executableTests.ts index 9904bf1f4b9..3afea34e1d8 100644 --- a/test/executable/executableTests.ts +++ b/test/executable/executableTests.ts @@ -19,7 +19,8 @@ import * as cp from "child_process"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; -import { createTempFile, denormalizeWinPath } from "../utils"; +import { denormalizeWinPath } from "../../src/utils"; +import { createTempFile } from "../utils"; // when tests are run with mocha from npm scripts CWD points to project root const EXECUTABLE_DIR = path.resolve(process.cwd(), "test", "executable"); diff --git a/test/rules/no-inferred-empty-object-type/test.ts.lint b/test/rules/no-inferred-empty-object-type/test.ts.lint index 2967deb4193..120b965fbda 100644 --- a/test/rules/no-inferred-empty-object-type/test.ts.lint +++ b/test/rules/no-inferred-empty-object-type/test.ts.lint @@ -84,3 +84,7 @@ new MultiParamsClass<() => void, () => {}>(); new MultiParamsClass<() => void, () => void>(); new MultiParamsClass<{ [x: number]: string }, () => void>(); new MultiParamsClass<{ [x: string]: string }, number>(); + +// don't crash with stack overflow +import {expect} from "chai"; +expect(1).to.eq(1); diff --git a/test/utils.ts b/test/utils.ts index fecf7456b6c..9c5faa6fc47 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -47,8 +47,3 @@ export function createTempFile(extension: string) { } return tmpfile; } - -// converts Windows normalized paths (with backwards slash `\`) to paths used by TypeScript (with forward slash `/`) -export function denormalizeWinPath(path: string): string { - return path.replace(/\\/g, "/"); -}