Skip to content
This repository was archived by the owner on Mar 25, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import { join as joinPaths } from "path";
import { findRule } from "../ruleLoader";
import { hasOwnProperty } from "../utils";

// tslint:disable object-literal-sort-keys
// tslint:disable object-literal-key-quotes
// tslint:disable object-literal-sort-keys object-literal-key-quotes object-literal-contextual-type
export const rules = {
// TypeScript Specific

Expand Down Expand Up @@ -132,6 +131,7 @@ export const rules = {
"no-use-before-declare": true,
"no-var-keyword": true,
"no-void-expression": true,
"object-literal-contextual-type": true,
"prefer-conditional-expression": true,
"radix": true,
"restrict-plus-operands": true,
Expand Down
3 changes: 1 addition & 2 deletions src/configs/latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
* limitations under the License.
*/

// tslint:disable object-literal-sort-keys
// tslint:disable:object-literal-key-quotes
// tslint:disable object-literal-sort-keys object-literal-key-quotes object-literal-contextual-type
export const rules = {
// added in v5.1
"align": {
Expand Down
1 change: 1 addition & 0 deletions src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* limitations under the License.
*/

// tslint:disable object-literal-contextual-type
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API break!! not down for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a comment, how does it break the API?

export const rules = {
"adjacent-overload-signatures": true,
"align": {
Expand Down
6 changes: 3 additions & 3 deletions src/rules/completedDocsRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class Rule extends Lint.Rules.TypedRule {
},
};

public static ARGUMENT_DESCRIPTOR_BLOCK = {
public static ARGUMENT_DESCRIPTOR_BLOCK: {} = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't even correct. it has a much more advanced type than the empty object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{} is a correct type. Anything but null or undefined or void is assignable to {}.

properties: {
[DESCRIPTOR_TAGS]: {
properties: {
Expand Down Expand Up @@ -139,7 +139,7 @@ export class Rule extends Lint.Rules.TypedRule {
type: "object",
};

public static ARGUMENT_DESCRIPTOR_CLASS = {
public static ARGUMENT_DESCRIPTOR_CLASS: {} = {
properties: {
[DESCRIPTOR_TAGS]: {
properties: {
Expand Down Expand Up @@ -285,7 +285,7 @@ export class Rule extends Lint.Rules.TypedRule {
typescriptOnly: false,
requiresTypeInfo: true,
};
/* tslint:enable:object-literal-sort-keys */
/* tslint:enable:object-literal-sort-keys object-literal-contextual-type */

private readonly exclusionFactory = new ExclusionFactory();

Expand Down
2 changes: 1 addition & 1 deletion src/rules/noUnusedVariableRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ function getUnusedCheckedProgram(program: ts.Program, checkParameters: boolean):

function makeUnusedCheckedProgram(program: ts.Program, checkParameters: boolean): ts.Program {
const originalOptions = program.getCompilerOptions();
const options = {
const options: ts.CompilerOptions = {
...originalOptions,
noEmit: true,
noUnusedLocals: true,
Expand Down
91 changes: 91 additions & 0 deletions src/rules/objectLiteralContextualTypeRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @license
* Copyright 2017 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { isArrayLiteralExpression, isObjectLiteralExpression, isPropertyAssignment } from "tsutils";
import * as ts from "typescript";

import * as Lint from "../index";

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "object-literal-contextual-type",
description: "Requires that every object literal has a contextual type.",
rationale: Lint.Utils.dedent`
An object literal with no type does not have excess properties checked.

For example:

interface I { x: number; }
function f(): I {
const res = { x: 0, y: 0 };
return res;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this error can be avoided so many ways that don't require a rather aggressive lint rule...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would it be avoided in general? I don't believe typescript's behavior with respect to object literals has changed much in the past year.
Often people will think they're getting a contextual type when they really aren't, e.g. a result from a callback:

interface Options { foo?: number; bar?: number }
const arr: Options[] = [1, 2, 3].map(n => ({ foo: n, baa: n }));

The error is fixed by changing to (n): Options, but I wouldn't want a lint rule to require all arrow functions to have explicit return types.
If there are better ways of avoiding this error I'd like to hear about them.

}

This has no compile error, but an excess property \`y\`.
The excess property can be detected by writing a type annotation \`const res: I = { x: 0, y: 0 };\`.`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
type: "functionality",
typescriptOnly: true,
requiresTypeInfo: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING = "Object literal has no contextual type.";

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, (ctx) => walk(ctx, program.getTypeChecker()));
}
}

function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
ts.forEachChild(ctx.sourceFile, function cb(node) {
if (isObjectLiteralExpression(node)) {
check(node);
}
ts.forEachChild(node, cb);
});

function check(node: ts.ObjectLiteralExpression): void {
// Allow `{}`, because obviously it does not have excess properties.
if (node.properties.length === 0) {
return;
}

// Allow an object literal inside another object literal or array literal (recursively) typed as 'any'.
// Normally the nested objects will not have a contextual type, so must traverse upwards to look for it.
let contextualNode: ts.Expression = node;

do {
if (checker.getContextualType(contextualNode) !== undefined) {
return;
}

const parent = contextualNode.parent!;
if (isPropertyAssignment(parent)) {
contextualNode = parent.parent;
} else if (isArrayLiteralExpression(parent)) {
contextualNode = parent;
} else {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
return;
}
} while (true);
}
}
2 changes: 1 addition & 1 deletion src/rules/preferReadonlyRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Rule extends Lint.Rules.TypedRule {
};

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
const options = {
const options: Options = {
onlyInlineLambdas: this.ruleArguments.indexOf(OPTION_ONLY_INLINE_LAMBDAS) !== -1,
};

Expand Down
1 change: 1 addition & 0 deletions src/rules/spaceBeforeFunctionParenRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getChildOfKind, hasModifier } from "tsutils";
import * as ts from "typescript";
import * as Lint from "../index";

// tslint:disable-next-line object-literal-contextual-type (https://github.com/palantir/tslint/issues/2428)
const ALWAYS_OR_NEVER = {
enum: ["always", "never"],
type: "string",
Expand Down
4 changes: 2 additions & 2 deletions src/rules/trailingCommaRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function normalize(value: OptionsJson["multiline"]): CustomOptionValue {
return typeof value === "string" ? fillOptions(value) : { ...defaultOptions, ...value };
}

/* tslint:disable:object-literal-sort-keys */
/* tslint:disable:object-literal-sort-keys object-literal-contextual-type */
const metadataOptionShape = {
anyOf: [
{
Expand All @@ -67,7 +67,7 @@ const metadataOptionShape = {
},
],
};
/* tslint:enable:object-literal-sort-keys */
/* tslint:enable:object-literal-sort-keys object-literal-contextual-type */

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
Expand Down
2 changes: 1 addition & 1 deletion src/rules/typedefWhitespaceRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type OptionType = "call-signature" | "index-signature" | "parameter" | "property
type OptionInput = Partial<Record<OptionType, Option>>;
type Options = Partial<Record<"left" | "right", OptionInput>>;

/* tslint:disable:object-literal-sort-keys */
// tslint:disable:object-literal-sort-keys object-literal-contextual-type (https://github.com/palantir/tslint/issues/2428)
const SPACE_OPTIONS = {
type: "string",
enum: ["nospace", "onespace", "space"],
Expand Down
5 changes: 3 additions & 2 deletions src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as path from "path";
import * as semver from "semver";
import * as ts from "typescript";

import { ILinterOptions } from "./index";
import { Replacement } from "./language/rule/rule";
import * as Linter from "./linter";
import { Logger } from "./runner";
Expand Down Expand Up @@ -78,7 +79,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[
throw new Error(JSON.stringify(error));
}

const parseConfigHost = {
const parseConfigHost: ts.ParseConfigHost = {
fileExists: fs.existsSync,
readDirectory: ts.sys.readDirectory,
readFile: (file: string) => fs.readFileSync(file, "utf8"),
Expand Down Expand Up @@ -139,7 +140,7 @@ export function runTest(testDirectory: string, rulesDirectory?: string | string[
program = ts.createProgram([fileCompileName], compilerOptions, compilerHost);
}

const lintOptions = {
const lintOptions: ILinterOptions = {
fix: false,
formatter: "prose",
formattersDirectory: "",
Expand Down
6 changes: 3 additions & 3 deletions src/verify/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
parseLine,
printLine,
} from "./lines";
import { errorComparator, LintError, lintSyntaxError } from "./lintError";
import { errorComparator, LintError, lintSyntaxError, PositionInFile } from "./lintError";

let scanner: ts.Scanner | undefined;

Expand Down Expand Up @@ -127,7 +127,7 @@ export function parseErrorsFromMarkup(text: string): LintError[] {
const errorLinesForCodeLines = createCodeLineNoToErrorsMap(lines);

const lintErrors: LintError[] = [];
function addError(errorLine: EndErrorLine, errorStartPos: { line: number; col: number }, lineNo: number) {
function addError(errorLine: EndErrorLine, errorStartPos: PositionInFile, lineNo: number) {
lintErrors.push({
startPos: errorStartPos,
endPos: { line: lineNo, col: errorLine.endCol },
Expand All @@ -140,7 +140,7 @@ export function parseErrorsFromMarkup(text: string): LintError[] {
// for each error marking on that line...
while (errorLinesForLineOfCode.length > 0) {
const errorLine = errorLinesForLineOfCode.shift();
const errorStartPos = { line: lineNo, col: errorLine!.startCol };
const errorStartPos: PositionInFile = { line: lineNo, col: errorLine!.startCol };

// if the error starts and ends on this line, add it now to list of errors
if (errorLine instanceof EndErrorLine) {
Expand Down
2 changes: 1 addition & 1 deletion test/configurationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("Configuration", () => {
});

it("arrayifies `extends`", () => {
const rawConfig = {
const rawConfig: RawConfigFile = {
extends: "a",
};
const expected = getEmptyConfig();
Expand Down
25 changes: 25 additions & 0 deletions test/rules/object-literal-contextual-type/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const ok: { x: 0 } = { x: 0 };

const notOk = { x: 0 };
~~~~~~~~ [0]

// `{}` OK.
const empty = {};

declare function f(obj: { x: number }): void;
f({ x: 0 });

// Allow any literal of type 'any',
// or nested inside an array or object literal in something else of type 'any'.
const isAny: any = {
a: { x: 0 },
b: [
{ x: 0 },
],
c() {
return { x: 0 };
~~~~~~~~ [0]
},
};

[0]: Object literal has no contextual type.
1 change: 1 addition & 0 deletions test/rules/object-literal-contextual-type/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions test/rules/object-literal-contextual-type/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"object-literal-contextual-type": true
}
}