move eslint rules from eslint-plugin-microsoft-typescript to scripts/eslint

This commit is contained in:
Alexander 2019-07-24 07:17:16 +03:00 committed by Alexander T
parent a79f598269
commit 0059763d8f
41 changed files with 1254 additions and 38 deletions

View File

@ -12,7 +12,7 @@
"es6": true
},
"plugins": [
"@typescript-eslint", "microsoft-typescript", "jsdoc", "no-null", "import"
"@typescript-eslint", "jsdoc", "no-null", "import"
],
"rules": {
"@typescript-eslint/adjacent-overload-signatures": "error",
@ -56,19 +56,19 @@
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unified-signatures": "error",
/** eslint-plugin-microsoft-typescript */
"microsoft-typescript/object-literal-surrounding-space": "error",
"microsoft-typescript/no-type-assertion-whitespace": "error",
"microsoft-typescript/type-operator-spacing": "error",
"microsoft-typescript/only-arrow-functions": ["error", {
/** scripts/eslint/rules */
"object-literal-surrounding-space": "error",
"no-type-assertion-whitespace": "error",
"type-operator-spacing": "error",
"only-arrow-functions": ["error", {
"allowNamedFunctions": true ,
"allowDeclarations": true
}],
"microsoft-typescript/no-double-space": "error",
"microsoft-typescript/boolean-trivia": "error",
"microsoft-typescript/no-in-operator": "error",
"microsoft-typescript/debug-assert": "error",
"microsoft-typescript/no-keywords": "error",
"no-double-space": "error",
"boolean-trivia": "error",
"no-in-operator": "error",
"debug-assert": "error",
"no-keywords": "error",
/** eslint-plugin-import */
"import/no-extraneous-dependencies": ["error", { "optionalDependencies": false }],
@ -151,8 +151,8 @@
"@typescript-eslint/prefer-function-type": "off",
"@typescript-eslint/unified-signatures": "off",
/** eslint-plugin-microsoft-typescript */
"microsoft-typescript/no-keywords": "off",
/** scripts/eslint/rules */
"no-keywords": "off",
/** eslint */
"no-var": "off"

View File

@ -11,6 +11,7 @@ src
tests
Jakefile.js
.eslintrc
.eslintignore
.editorconfig
.failed-tests
.git

View File

@ -318,20 +318,39 @@ task("clean-tests").description = "Cleans the outputs for the test infrastructur
const watchTests = () => watchProject("src/testRunner", cmdLineOptions);
const buildRules = () => buildProject("scripts/eslint");
task("build-rules", buildRules);
task("build-rules").description = "Compiles eslint rules to js";
const cleanRules = () => cleanProject("scripts/eslint");
cleanTasks.push(cleanRules);
task("clean-rules", cleanRules);
task("clean-rules").description = "Cleans the outputs for the eslint rules";
const runRulesTests = () => runConsoleTests("built/eslint/tests", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false);
task("run-rules-tests", series(buildRules, runRulesTests));
task("run-rules-tests").description = "Runs the eslint rule tests";
const lintFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("lint")); };
const lintFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("lint")); };
const eslint = async () => {
const args = [
"node_modules/eslint/bin/eslint", "-f", "autolinkable-stylish", "-c", ".eslintrc", "--ext", ".ts", "."
"node_modules/eslint/bin/eslint",
"--format", "autolinkable-stylish",
"--config", ".eslintrc",
"--ext", ".ts", ".",
"--rulesdir", "built/eslint/rules/",
];
if (cmdLineOptions.fix) {
args.push("--fix");
}
log(`Linting: ${args.join(" ")}`);
return exec(process.execPath, args);
}
const lint = series([lintFoldStart, eslint, lintFoldEnd]);
const lint = series([buildRules, lintFoldStart, eslint, lintFoldEnd]);
lint.displayName = "lint";
task("lint", lint);
task("lint").description = "Runs eslint on the compiler sources.";

View File

@ -55,6 +55,7 @@
"@types/travis-fold": "latest",
"@types/xml2js": "^0.4.0",
"@typescript-eslint/eslint-plugin": "1.13.0",
"@typescript-eslint/experimental-utils": "1.13.0",
"@typescript-eslint/parser": "1.13.0",
"async": "latest",
"azure-devops-node-api": "^8.0.0",
@ -68,7 +69,6 @@
"eslint-formatter-autolinkable-stylish": "1.0.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jsdoc": "15.6.1",
"eslint-plugin-microsoft-typescript": "0.2.0",
"eslint-plugin-no-null": "1.0.2",
"fancy-log": "latest",
"fs-extra": "^6.0.1",
@ -112,6 +112,7 @@
"gulp": "gulp",
"jake": "gulp",
"lint": "gulp lint",
"lint:test": "gulp run-rules-tests",
"setup-hooks": "node scripts/link-hooks.js",
"update-costly-tests": "node scripts/costly-tests.js"
},

View File

@ -0,0 +1,105 @@
import { SyntaxKind } from "typescript";
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/experimental-utils";
import { getEsTreeNodeToTSNodeMap, createRule } from "./utils";
export = createRule({
name: "boolean-trivia",
meta: {
docs: {
description: ``,
category: "Best Practices",
recommended: "error",
},
messages: {
booleanTriviaArgumentError: `Tag argument with parameter name`,
booleanTriviaArgumentSpaceError: `There should be 1 space between an argument and its comment`,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
const esTreeNodeToTSNodeMap = getEsTreeNodeToTSNodeMap(context.parserServices);
const sourceCode = context.getSourceCode();
const sourceCodeText = sourceCode.getText();
const isSetOrAssert = (name: string): boolean => name.startsWith("set") || name.startsWith("assert");
const isTrivia = (node: TSESTree.Expression): boolean => {
const tsNode = esTreeNodeToTSNodeMap.get(node);
if (tsNode.kind === SyntaxKind.Identifier) {
return tsNode.originalKeywordKind === SyntaxKind.UndefinedKeyword;
}
return [SyntaxKind.TrueKeyword, SyntaxKind.FalseKeyword, SyntaxKind.NullKeyword].indexOf(tsNode.kind) >= 0;
};
const shouldIgnoreCalledExpression = (node: TSESTree.CallExpression): boolean => {
if (node.callee && node.callee.type === AST_NODE_TYPES.MemberExpression) {
const methodName = node.callee.property.type === AST_NODE_TYPES.Identifier
? node.callee.property.name
: "";
if (isSetOrAssert(methodName)) {
return true;
}
return ["apply", "call", "equal", "fail", "isTrue", "output", "stringify", "push"].indexOf(methodName) >= 0;
}
if (node.callee && node.callee.type === AST_NODE_TYPES.Identifier) {
const functionName = node.callee.name;
if (isSetOrAssert(functionName)) {
return true;
}
return [
"createImportSpecifier",
"createAnonymousType",
"createSignature",
"createProperty",
"resolveName",
"contains",
].indexOf(functionName) >= 0;
}
return false;
};
const checkArg = (node: TSESTree.Expression): void => {
if (!isTrivia(node)) {
return;
}
const comments = sourceCode.getCommentsBefore(node);
if (!comments || comments.length !== 1 || comments[0].type !== "Block") {
context.report({ messageId: "booleanTriviaArgumentError", node });
return;
}
const argRangeStart = node.range[0];
const commentRangeEnd = comments[0].range[1];
const hasNewLine = sourceCodeText.slice(commentRangeEnd, argRangeStart).indexOf("\n") >= 0;
if (argRangeStart !== commentRangeEnd + 1 && !hasNewLine) {
context.report({ messageId: "booleanTriviaArgumentSpaceError", node });
}
};
const checkBooleanTrivia = (node: TSESTree.CallExpression) => {
if (shouldIgnoreCalledExpression(node)) {
return;
}
for (const arg of node.arguments) {
checkArg(arg);
}
};
return {
CallExpression: checkBooleanTrivia,
};
},
});

View File

@ -0,0 +1,60 @@
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "debug-assert",
meta: {
docs: {
description: ``,
category: "Possible Errors",
recommended: "error",
},
messages: {
secondArgumentDebugAssertError: `Second argument to 'Debug.assert' should be a string literal`,
thirdArgumentDebugAssertError: `Third argument to 'Debug.assert' should be a string literal or arrow function`,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
const isArrowFunction = (node: TSESTree.Node) => node.type === AST_NODE_TYPES.ArrowFunctionExpression;
const isStringLiteral = (node: TSESTree.Node): boolean => (
(node.type === AST_NODE_TYPES.Literal && typeof node.value === "string") || node.type === AST_NODE_TYPES.TemplateLiteral
);
const isDebugAssert = (node: TSESTree.MemberExpression): boolean => (
node.object.type === AST_NODE_TYPES.Identifier
&& node.object.name === "Debug"
&& node.property.type === AST_NODE_TYPES.Identifier
&& node.property.name === "assert"
);
const checkDebugAssert = (node: TSESTree.CallExpression) => {
const args = node.arguments;
const argsLen = args.length;
if (!(node.callee.type === AST_NODE_TYPES.MemberExpression && isDebugAssert(node.callee)) || argsLen < 2) {
return;
}
const message1Node = args[1];
if (message1Node && !isStringLiteral(message1Node)) {
context.report({ messageId: "secondArgumentDebugAssertError", node: message1Node });
}
if (argsLen < 3) {
return;
}
const message2Node = args[2];
if (message2Node && (!isStringLiteral(message2Node) && !isArrowFunction(message2Node))) {
context.report({ messageId: "thirdArgumentDebugAssertError", node: message2Node });
}
};
return {
CallExpression: checkDebugAssert,
};
},
});

View File

@ -0,0 +1,81 @@
import { TSESTree } from "@typescript-eslint/experimental-utils";
import { SyntaxKind } from "typescript";
import { getEsTreeNodeToTSNodeMap, createRule } from "./utils";
export = createRule({
name: "no-double-space",
meta: {
docs: {
description: ``,
category: "Stylistic Issues",
recommended: "error",
},
messages: {
noDoubleSpaceError: `Use only one space`,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
const esTreeNodeToTSNodeMap = getEsTreeNodeToTSNodeMap(context.parserServices);
const sourceCode = context.getSourceCode();
const lines = sourceCode.getLines();
const isLiteral = (node: TSESTree.Node | null) => {
if (!node) {
return false;
}
const tsNode = esTreeNodeToTSNodeMap.get(node);
if (!tsNode) {
return false;
}
return [
SyntaxKind.NoSubstitutionTemplateLiteral,
SyntaxKind.RegularExpressionLiteral,
SyntaxKind.TemplateMiddle,
SyntaxKind.StringLiteral,
SyntaxKind.TemplateHead,
SyntaxKind.TemplateTail,
].indexOf(tsNode.kind) >= 0;
};
const checkDoubleSpace = (node: TSESTree.Node) => {
lines.forEach((line, index) => {
const firstNonSpace = /\S/.exec(line);
if (!firstNonSpace || line.includes("@param")) {
return;
}
// Allow common uses of double spaces
// * To align `=` or `!=` signs
// * To align comments at the end of lines
// * To indent inside a comment
// * To use two spaces after a period
// * To include aligned `->` in a comment
const rgx = /[^/*. ][ ]{2}[^-!/= ]/g;
rgx.lastIndex = firstNonSpace.index;
const doubleSpace = rgx.exec(line);
if (!doubleSpace) {
return;
}
const locIndex = sourceCode.getIndexFromLoc({ column: doubleSpace.index, line: index + 1 });
const sourceNode = sourceCode.getNodeByRangeIndex(locIndex);
if (isLiteral(sourceNode)) {
return;
}
context.report({ messageId: "noDoubleSpaceError", node, loc: { line: index + 1, column: doubleSpace.index + 1 } });
});
};
return {
Program: checkDoubleSpace,
};
},
});

View File

@ -0,0 +1,32 @@
import { TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "no-in-operator",
meta: {
docs: {
description: ``,
category: "Best Practices",
recommended: "error",
},
messages: {
noInOperatorError: `Don't use the 'in' keyword - use 'hasProperty' to check for key presence instead`,
},
schema: [],
type: "suggestion",
},
defaultOptions: [],
create(context) {
const IN_OPERATOR = "in";
const checkInOperator = (node: TSESTree.BinaryExpression) => {
if (node.operator === IN_OPERATOR) {
context.report({ messageId: "noInOperatorError", node });
}
};
return {
BinaryExpression: checkInOperator,
};
},
});

View File

@ -0,0 +1,70 @@
import { TSESTree, AST_NODE_TYPES } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "no-keywords",
meta: {
docs: {
description: `disallows the use of certain TypeScript keywords as variable or parameter names`,
category: "Stylistic Issues",
recommended: "error",
},
messages: {
noKeywordsError: `{{ name }} clashes with keyword/type`,
},
schema: [{
properties: {
properties: { type: "boolean" },
keywords: { type: "boolean" },
},
type: "object",
}],
type: "suggestion",
},
defaultOptions: [],
create(context) {
const keywords = [
"Undefined",
"undefined",
"Boolean",
"boolean",
"String",
"string",
"Number",
"number",
"any",
];
const hasKeyword = (name: string) => keywords.includes(name);
const shouldReport = (node: TSESTree.Identifier) => {
if (!node || !node.parent || !hasKeyword(node.name)) {
return false;
}
const parent = node.parent;
if (parent.type === AST_NODE_TYPES.FunctionDeclaration || parent.type === AST_NODE_TYPES.FunctionExpression) {
return !(parent.id && hasKeyword(parent.id.name));
}
if (parent.type === AST_NODE_TYPES.TSMethodSignature && parent.key.type === AST_NODE_TYPES.Identifier) {
return !(parent.key && hasKeyword(parent.key.name));
}
return [
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.VariableDeclarator,
AST_NODE_TYPES.TSFunctionType,
].includes(node.parent.type);
};
const check = (node: TSESTree.Identifier) => {
if (shouldReport(node)) {
context.report({ messageId: "noKeywordsError", data: { name: node.name }, node });
}
};
return { Identifier: check };
},
});

View File

@ -0,0 +1,43 @@
import { TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "no-type-assertion-whitespace",
meta: {
docs: {
description: ``,
category: "Stylistic Issues",
recommended: "error",
},
messages: {
noTypeAssertionWhitespace: `Excess trailing whitespace found around type assertion`,
},
schema: [],
type: "problem",
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
const checkTypeAssertionWhitespace = (node: TSESTree.TSTypeAssertion) => {
const leftToken = sourceCode.getLastToken(node.typeAnnotation);
const rightToken = sourceCode.getFirstToken(node.expression);
if (!leftToken || !rightToken) {
return;
}
if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) {
context.report({
messageId: "noTypeAssertionWhitespace",
node,
loc: { column: leftToken.loc.end.column + 1, line: leftToken.loc.end.line },
});
}
};
return {
TSTypeAssertion: checkTypeAssertionWhitespace,
};
},
});

View File

@ -0,0 +1,71 @@
import { TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "object-literal-surrounding-space",
meta: {
docs: {
description: ``,
category: "Stylistic Issues",
recommended: "error",
},
messages: {
leadingExcessStringError: `No trailing whitespace found on single-line object literal`,
leadingStringError: `No leading whitespace found on single-line object literal`,
trailingExcessStringError: `Excess trailing whitespace found on single-line object literal.`,
trailingStringError: `No trailing whitespace found on single-line object literal`,
},
schema: [],
type: "suggestion",
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
const SPACE_SYMBOL = " ";
const CLOSE_SYMBOL = "}";
const OPEN_SYMBOL = "{";
const manySpaces = (text: string, startIndex: number): boolean => (
[startIndex, startIndex + 1].every(i => text.charAt(i) === SPACE_SYMBOL)
);
const checkObjectLiteralSurroundingSpace = (node: TSESTree.ObjectExpression) => {
const text = sourceCode.getText(node);
const startLine = node.loc.start.line;
const endLine = node.loc.end.line;
if (!node.properties.length || !text.match(/^{[^\n]+}$/g)) {
return;
}
if (text.charAt(0) === OPEN_SYMBOL) {
if (text.charAt(1) !== SPACE_SYMBOL) {
context.report({ messageId: "leadingStringError", node });
}
if (manySpaces(text, 1)) {
context.report({ messageId: "leadingExcessStringError", node, loc: { column: node.loc.start.column + 1, line: startLine } });
}
}
if (text.charAt(text.length - 1) === CLOSE_SYMBOL) {
const index = text.length - 2;
const endColumn = node.loc.end.column;
if (text.charAt(index) !== SPACE_SYMBOL) {
context.report({ messageId: "trailingStringError", node, loc: { column: endColumn - 1, line: endLine } });
}
if (manySpaces(text, index - 1)) {
context.report({ messageId: "trailingExcessStringError", node, loc: { column: endColumn - 2, line: endLine } });
}
}
};
return {
ObjectExpression: checkObjectLiteralSurroundingSpace,
};
},
});

View File

@ -0,0 +1,91 @@
import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
type MessageId = "onlyArrowFunctionsError";
type Options = [{
allowNamedFunctions?: boolean;
allowDeclarations?: boolean;
}];
export = createRule<Options, MessageId>({
name: "only-arrow-functions",
meta: {
docs: {
description: `Disallows traditional (non-arrow) function expressions.`,
category: "Best Practices",
recommended: "error",
},
messages: {
onlyArrowFunctionsError: "non-arrow functions are forbidden",
},
schema: [{
additionalProperties: false,
properties: {
allowNamedFunctions: { type: "boolean" },
allowDeclarations: { type: "boolean" },
},
type: "object",
}],
type: "suggestion",
},
defaultOptions: [{
allowNamedFunctions: false,
allowDeclarations: false,
}],
create(context, [{ allowNamedFunctions, allowDeclarations }]) {
const isThisParameter = (node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression) => (
node.params.length && !!node.params.find(param => param.type === AST_NODE_TYPES.Identifier && param.name === "this")
);
const isMethodType = (node: TSESTree.Node) => {
const types = [
AST_NODE_TYPES.MethodDefinition,
AST_NODE_TYPES.Property,
];
const parent = node.parent;
if (!parent) {
return false;
}
return node.type === AST_NODE_TYPES.FunctionExpression && types.includes(parent.type);
};
const stack: boolean[] = [];
const enterFunction = () => {
stack.push(false);
};
const markThisUsed = () => {
if (stack.length) {
stack[stack.length - 1] = true;
}
};
const exitFunction = (node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression) => {
const methodUsesThis = stack.pop();
if (node.type === AST_NODE_TYPES.FunctionDeclaration && allowDeclarations) {
return;
}
if ((allowNamedFunctions && node.id !== null) || isMethodType(node)) { // eslint-disable-line no-null/no-null
return;
}
if (!(node.generator || methodUsesThis || isThisParameter(node))) {
context.report({ messageId: "onlyArrowFunctionsError", node });
}
};
return {
"FunctionDeclaration": enterFunction,
"FunctionDeclaration:exit": exitFunction,
"FunctionExpression": enterFunction,
"FunctionExpression:exit": exitFunction,
"ThisExpression": markThisUsed,
};
},
});

View File

@ -0,0 +1,44 @@
import { TSESTree, AST_TOKEN_TYPES } from "@typescript-eslint/experimental-utils";
import { createRule } from "./utils";
export = createRule({
name: "type-operator-spacing",
meta: {
docs: {
description: ``,
category: "Stylistic Issues",
recommended: "error",
},
messages: {
typeOperatorSpacingError: `The '|' and '&' operators must be surrounded by spaces`,
},
schema: [],
type: "suggestion",
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
const tokens = ["|", "&"];
const text = sourceCode.getText();
const checkTypeOperatorSpacing = (node: TSESTree.TSIntersectionType | TSESTree.TSUnionType) => {
node.types.forEach(node => {
const token = sourceCode.getTokenBefore(node);
if (!!token && token.type === AST_TOKEN_TYPES.Punctuator && tokens.indexOf(token.value) >= 0) {
const [start, end] = token.range;
if (/\S/.test(text[start - 1]) || /\S/.test(text[end])) {
context.report({ messageId: "typeOperatorSpacingError", node: token });
}
}
});
};
return {
TSIntersectionType: checkTypeOperatorSpacing,
TSUnionType: checkTypeOperatorSpacing,
};
},
});

View File

@ -0,0 +1,18 @@
import { ParserServices, ESLintUtils } from "@typescript-eslint/experimental-utils";
export const createRule = ESLintUtils.RuleCreator(() => "");
export const getTypeChecker = (parserServices: ParserServices | undefined) => {
if (!parserServices || !parserServices.program || !parserServices.program.getTypeChecker) {
throw new Error("'typeChecker' was not found");
}
return parserServices.program.getTypeChecker();
};
export const getEsTreeNodeToTSNodeMap = (parserServices: ParserServices | undefined) => {
if (!parserServices || !parserServices.esTreeNodeToTSNodeMap) {
throw new Error("'esTreeNodeToTSNodeMap' was not found");
}
return parserServices.esTreeNodeToTSNodeMap;
};

View File

@ -0,0 +1,73 @@
import { RuleTester, ROOT_DIR } from "./support/RuleTester";
import rule = require("../rules/boolean-trivia");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
tsconfigRootDir: ROOT_DIR,
ecmaFeatures: {},
ecmaVersion: 6,
sourceType: "module",
project: "./tsconfig.json",
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("boolean-trivia", rule, {
valid: [{
code: `
const fn = (prop: boolean) => {};
fn(/* boolean prop */ true);
`,
}, {
code: `
const fn = (prop: null) => {};
fn(/* null prop */ null);
`,
}, {
code: `
const fn = (prop: null) => {};
fn(/*null prop*/ null);
`,
}, {
code: `
const fn = (prop: boolean) => {};
fn(/* comment */
false
);
`,
}, {
code: `
const fn = (prop: boolean) => {};
fn.apply(null, true);
`,
}],
invalid: [{
code: `
const fn = (prop: null) => {};
fn(null);
`,
errors: [{ messageId: "booleanTriviaArgumentError" }],
}, {
code: `
const fn = (prop: boolean) => {};
fn(false);
`,
errors: [{ messageId: "booleanTriviaArgumentError" }],
}, {
code: `
const fn = (prop: boolean) => {};
fn(/* boolean arg */false);
`,
errors: [{ messageId: "booleanTriviaArgumentSpaceError" }],
}, {
code: `
const fn = (prop: boolean) => {};
fn(/* first comment */ /* second comment */ false);
`,
errors: [{ messageId: "booleanTriviaArgumentError" }],
}],
});

View File

@ -0,0 +1,40 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/debug-assert");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("debug-assert", rule, {
valid: [{
code: `Debug.assert(true)`,
}, {
code: `Debug.assert(true, 'error message')`,
}, {
code: `Debug.assert(true, 'error message 1', 'error message 2')`,
}, {
code: `Debug.assert(true, 'error message 1', () => {})`,
}, {
code: "Debug.assert(true, `error message 1`, () => {})",
}, {
code: `Debug.assert(true, "error message 1", () => {})`,
}],
invalid: [{
code: `Debug.assert(true, 1)`,
errors: [{ messageId: "secondArgumentDebugAssertError" }],
}, {
code: `Debug.assert(true, 'error message', 1)`,
errors: [{ messageId: "thirdArgumentDebugAssertError" }],
}, {
code: `Debug.assert(true, null, 1)`,
errors: [{
messageId: "secondArgumentDebugAssertError",
}, {
messageId: "thirdArgumentDebugAssertError",
}],
}],
});

View File

@ -0,0 +1,115 @@
import { RuleTester, ROOT_DIR } from "./support/RuleTester";
import rule = require("../rules/no-double-space");
const ruleTester = new RuleTester({
parser: require.resolve("@typescript-eslint/parser"),
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
tsconfigRootDir: ROOT_DIR,
ecmaFeatures: {},
ecmaVersion: 6,
sourceType: "module",
project: "./tsconfig.json",
},
});
ruleTester.run("no-double-space", rule, {
valid: [{
code: `const a = {};`,
}, {
code: `function fn() {}`,
}, {
code: `const a = " ";`,
}, {
code: `// ^ ^`,
}, {
code: `class Cl {}`,
}, {
code: `// comment `,
}, {
code: `/* comment */`,
}, {
code: `" string ";`,
}, {
code: `/ regexp /g;`,
}, {
code: `const rgx = / regexp /g;`,
}, {
code: "const str = ` string template`;",
}, {
code: ` // comment`,
}, {
code: ` /* comment */`,
}, {
code: `// `,
}, {
code: `
const a =
1;
`,
}, {
code: `
/**
* comment
*/
`,
}, {
code: `
// comment
// - comment
// - comment
`,
}, {
code: `
interface Props {
prop: string[]; // comment prop
propB: string[]; // comment propB
}
`,
}, {
code: `
/**
* Returns a JSON-encoded value of the type: string[]
*
* @param exclude A JSON encoded string[] containing the paths to exclude
* when enumerating the directory.
*/
`,
}, {
code: `
const obj = {
content: "function f() { 1; }",
};
`,
}],
invalid: [{
code: `const a = {};`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 6 }],
}, {
code: `function fn() {}`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 9 }],
}, {
code: `class Cl {}`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 6 }],
}, {
code: "const str = ` string template`;",
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }],
}, {
code: `/** comment */`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }],
}, {
code: `/** comment with many spaces */`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 12 }],
}, {
code: `// comment with many spaces`,
errors: [{ messageId: "noDoubleSpaceError", line: 1, column: 11 }],
}, {
code: `
const a = 1;
const b = 2;
const c = 3;
`.trim(),
errors: [{ messageId: "noDoubleSpaceError", line: 3, column: 10 }],
}],
});

View File

@ -0,0 +1,26 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/no-in-operator");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("no-in-operator", rule, {
valid: [{
code: `
const prop = {};
prop.hasProperty('a');
`,
}],
invalid: [{
code: `
const prop = {};
prop in 'a';
`,
errors: [{ messageId: "noInOperatorError" }],
}],
});

View File

@ -0,0 +1,77 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/no-keywords");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("no-keywords", rule, {
valid: [{
code: `const a = {};`,
}, {
code: `function a() {};`,
}, {
code: `const x = function string() {};`,
}, {
code: `const y = function () {};`,
}, {
code: `const y = () => {};`,
}, {
code: `
class A {
b = () => {}
a() {}
number() {}
}
`,
}, {
code: `
interface A {
b(): void;
}
`,
}, {
code: `
const obj = {
a: null,
b() {},
c: function (c: number) {},
string: function d (d: string) {},
e: () => {},
};
`,
}],
invalid: [{
code: `const number = 1;`,
errors: [{ messageId: "noKeywordsError" }],
}, {
code: `function x(number: number) {};`,
errors: [{ messageId: "noKeywordsError" }],
}, {
code: `const y = function (number: number) {};`,
errors: [{ messageId: "noKeywordsError" }],
}, {
code: `const y = (number: number) => {};`,
errors: [{ messageId: "noKeywordsError" }],
}, {
code: `
class A {
b = function (any: any) {};
a(number: number) {}
}
`,
errors: [{ messageId: "noKeywordsError" }, { messageId: "noKeywordsError" }],
}, {
code: `
interface A {
a(number: number): void;
b: (any: any) => void;
}
`,
errors: [{ messageId: "noKeywordsError" }, { messageId: "noKeywordsError" }],
}],
});

View File

@ -0,0 +1,31 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/no-type-assertion-whitespace");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("no-type-assertion-whitespace", rule, {
valid: [{
code: `const a = <number>1`,
}, {
code: `const s = <Type>(new Symbol(1, 'name'));`,
}],
invalid: [{
code: `const a = <number> 1`,
errors: [{ messageId: "noTypeAssertionWhitespace", column: 19, line: 1 }],
}, {
code: `const a = <number> 1`,
errors: [{ messageId: "noTypeAssertionWhitespace", column: 19, line: 1 }],
}, {
code: `const a = <number> 1`,
errors: [{ messageId: "noTypeAssertionWhitespace", column: 19, line: 1 }],
}, {
code: `const s = <Type> (new Symbol(1, 'name'));`,
errors: [{ messageId: "noTypeAssertionWhitespace", column: 17, line: 1 }],
}],
});

View File

@ -0,0 +1,40 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/object-literal-surrounding-space");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("object-literal-surrounding-space", rule, {
valid: [{
code: `const prop = {}`,
}, {
code: `const prop = { }`,
}, {
code: `const prop = { x: 1 }`,
}],
invalid: [{
code: `const prop = {x: 1}`,
errors: [{
messageId: "leadingStringError",
}, {
messageId: "trailingStringError",
}],
}, {
code: `const prop = { x: 1 }`,
errors: [{ messageId: "leadingExcessStringError" }],
}, {
code: `const prop = { x: 1 }`,
errors: [{ messageId: "trailingExcessStringError" }],
}, {
code: `const prop = { x: 1}`,
errors: [{ messageId: "trailingStringError" }],
}, {
code: `const prop = {x: 1 }`,
errors: [{ messageId: "leadingStringError" }],
}],
});

View File

@ -0,0 +1,101 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/only-arrow-functions");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("only-arrow-functions", rule, {
valid: [{
code: `const a = () => {};`,
}, {
code: `((func) => func())(() => {});`,
}, {
code: `function* generator() {}`,
}, {
code: `let generator = function*() {}`,
}, {
code: `function hasThisParameter(this) {}`,
}, {
code: `let hasThisParameter = function(this) {}`,
}, {
code: `let usesThis = function() { this; }`,
}, {
code: `let usesThis2 = function(foo = this) {}`,
}, {
code: `
let fn = function fn() {};
function z() {};
`,
options: [{ allowNamedFunctions: true }],
}, {
code: `
function fn() {};
let generator = function*() {}
function hasThisParameter(this) {}
let hasThisParameter = function(this) {}
`,
options: [{ allowDeclarations: true }],
}, {
code: `
class A {
test() {}
}
`,
}, {
code: `
const obj = {
test() {}
}
`,
}],
invalid: [{
code: `function foo(a: any): any {}`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `let b = function () {};`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `function c() {}`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `((func) => func())(function e(): void {});`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `
let notUsesThis = function() {
function f() { this; }
}
`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `
let notUsesThis2 = function() {
return class { method() { this; } }
}
`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `export function exported() {}`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `async function asyncFunction() {}`,
errors: [{ messageId: "onlyArrowFunctionsError" }],
}, {
code: `
let b = function () {};
((func) => func())(function e(): void {});
`,
options: [{ allowDeclarations: true }],
errors: [{ messageId: "onlyArrowFunctionsError" }, { messageId: "onlyArrowFunctionsError" }],
}, {
code: `const x = function() {}`,
options: [{ allowNamedFunctions: true }],
errors: [{ messageId: "onlyArrowFunctionsError" }],
}],
});

View File

@ -0,0 +1,5 @@
import * as path from "path";
import { TSESLint } from "@typescript-eslint/experimental-utils";
export const ROOT_DIR = path.resolve(__dirname);
export const RuleTester = TSESLint.RuleTester;

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"esModuleInterop": true,
"target": "es5",
"module": "commonjs",
"strict": true,
"lib": ["es2015", "es2017", "esnext"]
}
}

View File

@ -0,0 +1,35 @@
import { RuleTester } from "./support/RuleTester";
import rule = require("../rules/type-operator-spacing");
const ruleTester = new RuleTester({
parserOptions: {
warnOnUnsupportedTypeScriptVersion: false,
},
parser: require.resolve("@typescript-eslint/parser"),
});
ruleTester.run("type-operator-spacing", rule, {
valid: [{
code: `type T = string | number`,
}, {
code: `type T = string & number`,
}, {
code: `function fn(): string | number {}`,
}, {
code: `function fn(): string & number {}`,
}],
invalid: [{
code: `type T = string|number`,
errors: [{ messageId: "typeOperatorSpacingError" }],
}, {
code: `type T = string&number`,
errors: [{ messageId: "typeOperatorSpacingError" }],
}, {
code: `function fn(): string|number {}`,
errors: [{ messageId: "typeOperatorSpacingError" }],
}, {
code: `function fn(): string&number {}`,
errors: [{ messageId: "typeOperatorSpacingError" }],
}],
});

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"suppressImplicitAnyIndexErrors": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"esModuleInterop": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"skipLibCheck": true,
"declaration": false,
"noResolve": false,
"strict": true,
"module": "commonjs",
"target": "es5",
"outDir": "../../built/eslint",
"lib": ["es2015", "es2016"]
},
"include": [
"rules",
"tests",
"tests/support/*.json"
]
}

View File

@ -927,7 +927,7 @@ namespace ts {
}
}
function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void {
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line microsoft-typescript/no-in-operator
addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator
}
function errorAndMaybeSuggestAwait(

View File

@ -115,7 +115,7 @@ namespace ts {
// The global Map object. This may not be available, so we must test for it.
declare const Map: (new <T>() => Map<T>) | undefined;
// Internet Explorer's Map doesn't support iteration, so don't use it.
// eslint-disable-next-line microsoft-typescript/no-in-operator
// eslint-disable-next-line no-in-operator
export const MapCtr = typeof Map !== "undefined" && "entries" in Map.prototype ? Map : shimMap();
// Keep the class inside a function so it doesn't get compiled if it's not used.
@ -222,7 +222,7 @@ namespace ts {
}
has(key: string): boolean {
// eslint-disable-next-line microsoft-typescript/no-in-operator
// eslint-disable-next-line no-in-operator
return key in this.data;
}

View File

@ -75,7 +75,7 @@ namespace ts {
if (typeof value === "number") {
return createNumericLiteral(value + "");
}
// eslint-disable-next-line microsoft-typescript/no-in-operator
// eslint-disable-next-line no-in-operator
if (typeof value === "object" && "base10Value" in value) { // PseudoBigInt
return createBigIntLiteral(pseudoBigIntToString(value) + "n");
}

View File

@ -68,7 +68,7 @@ namespace ts {
return sourceIndex;
}
/* eslint-disable microsoft-typescript/boolean-trivia, no-null/no-null */
/* eslint-disable boolean-trivia, no-null/no-null */
function setSourceContent(sourceIndex: number, content: string | null) {
enter();
if (content !== null) {
@ -80,7 +80,7 @@ namespace ts {
}
exit();
}
/* eslint-enable microsoft-typescript/boolean-trivia, no-null/no-null */
/* eslint-enable boolean-trivia, no-null/no-null */
function addName(name: string) {
enter();

View File

@ -1205,7 +1205,7 @@ namespace FourSlash {
const sort = (locations: ReadonlyArray<ts.RenameLocation> | undefined) =>
locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start);
assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => {
const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line microsoft-typescript/no-in-operator
const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; // eslint-disable-line no-in-operator
const { contextRangeIndex } = (range.marker && range.marker.data || {}) as { contextRangeIndex?: number; };
return {
fileName: range.fileName,
@ -3160,7 +3160,7 @@ namespace FourSlash {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName);
}
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences); // eslint-disable-line microsoft-typescript/no-in-operator
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences); // eslint-disable-line no-in-operator
}
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences) || ts.emptyArray;

View File

@ -4,7 +4,7 @@ namespace Harness.LanguageService {
const proxy = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null
const langSvc: any = info.languageService;
for (const k of Object.keys(langSvc)) {
// eslint-disable-next-line microsoft-typescript/only-arrow-functions
// eslint-disable-next-line only-arrow-functions
proxy[k] = function () {
return langSvc[k].apply(langSvc, arguments);
};
@ -799,7 +799,7 @@ namespace Harness.LanguageService {
create(info: ts.server.PluginCreateInfo) {
const proxy = makeDefaultProxy(info);
const langSvc: any = info.languageService;
// eslint-disable-next-line microsoft-typescript/only-arrow-functions
// eslint-disable-next-line only-arrow-functions
proxy.getQuickInfoAtPosition = function () {
const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments);
if (parts.displayParts.length > 0) {

View File

@ -363,7 +363,7 @@ namespace Playback {
function recordReplay<T extends ts.AnyFunction>(original: T, underlying: any) {
function createWrapper(record: T, replay: T): T {
// eslint-disable-next-line microsoft-typescript/only-arrow-functions
// eslint-disable-next-line only-arrow-functions
return <any>(function () {
if (replayLog !== undefined) {
return replay.apply(undefined, arguments);

View File

@ -1290,7 +1290,7 @@ namespace ts.server {
const pluginModule = pluginModuleFactory({ typescript: ts });
const newLS = pluginModule.create(info);
for (const k of Object.keys(this.languageService)) {
// eslint-disable-next-line microsoft-typescript/no-in-operator
// eslint-disable-next-line no-in-operator
if (!(k in newLS)) {
this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
(newLS as any)[k] = (this.languageService as any)[k];

View File

@ -607,7 +607,7 @@ namespace ts.codefix {
}
// return undefined argName when arg is null or undefined
// eslint-disable-next-line microsoft-typescript/no-in-operator
// eslint-disable-next-line no-in-operator
if (!name || "identifier" in name && name.identifier.text === "undefined") {
return undefined;
}

View File

@ -1439,7 +1439,7 @@ namespace ts.FindAllReferences.Core {
}
function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void {
const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line microsoft-typescript/no-in-operator
const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line no-in-operator
const addRef = state.referenceAdder(symbol);
if (state.options.implementations) {
addImplementationReferences(referenceLocation, addRef, state);

View File

@ -35,7 +35,7 @@ namespace ts {
}
private assertHasRealPosition(message?: string) {
// eslint-disable-next-line microsoft-typescript/debug-assert
// eslint-disable-next-line debug-assert
Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation");
}

View File

@ -17,7 +17,7 @@
let debugObjectHost: { CollectGarbage(): void } = (function (this: any) { return this; })(); // eslint-disable-line prefer-const
// We need to use 'null' to interface with the managed side.
/* eslint-disable microsoft-typescript/no-in-operator */
/* eslint-disable no-in-operator */
/* @internal */
namespace ts {
@ -1275,7 +1275,7 @@ namespace ts {
}
}
/* eslint-enable microsoft-typescript/no-in-operator */
/* eslint-enable no-in-operator */
/// TODO: this is used by VS, clean this up on both sides of the interface
/* @internal */

View File

@ -394,11 +394,11 @@ namespace ts.SignatureHelp {
// not enough to put us in the substitution expression; we should consider ourselves part of
// the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
//
/* eslint-disable microsoft-typescript/no-double-space */
/* eslint-disable no-double-space */
// Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # `
// ^ ^ ^ ^ ^ ^ ^ ^ ^
// Case: 1 1 3 2 1 3 2 2 1
/* eslint-enable microsoft-typescript/no-double-space */
/* eslint-enable no-double-space */
Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
if (isTemplateLiteralToken(node)) {
if (isInsideTemplateLiteral(node, position, sourceFile)) {

View File

@ -47,7 +47,7 @@ namespace Harness.Parallel.Host {
constructor(info: ErrorInfo | TestInfo) {
super(info.name[info.name.length - 1]);
this.info = info;
this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line microsoft-typescript/no-in-operator
this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line no-in-operator
this.pending = false;
}
}
@ -540,7 +540,7 @@ namespace Harness.Parallel.Host {
function replayTest(runner: Mocha.Runner, test: RemoteTest) {
runner.emit("test", test);
if (test.isFailed()) {
runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line microsoft-typescript/no-in-operator
runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line no-in-operator
}
else {
runner.emit("pass", test);

View File

@ -665,7 +665,7 @@ namespace ts.projectSystem {
export function openFilesForSession(files: ReadonlyArray<File | { readonly file: File | string, readonly projectRootPath: string }>, session: server.Session): void {
for (const file of files) {
session.executeCommand(makeSessionRequest<protocol.OpenRequestArgs>(CommandNames.Open,
"projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line microsoft-typescript/no-in-operator
"projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line no-in-operator
}
}