Skip to content

Commit

Permalink
move eslint rules from eslint-plugin-microsoft-typescript to scripts/…
Browse files Browse the repository at this point in the history
…eslint
  • Loading branch information
a-tarasyuk committed Jul 24, 2019
1 parent a79f598 commit 0059763
Show file tree
Hide file tree
Showing 41 changed files with 1,254 additions and 38 deletions.
26 changes: 13 additions & 13 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 }],
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ src
tests
Jakefile.js
.eslintrc
.eslintignore
.editorconfig
.failed-tests
.git
Expand Down
23 changes: 21 additions & 2 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
105 changes: 105 additions & 0 deletions scripts/eslint/rules/boolean-trivia.ts
Original file line number Diff line number Diff line change
@@ -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,
};
},
});
60 changes: 60 additions & 0 deletions scripts/eslint/rules/debug-assert.ts
Original file line number Diff line number Diff line change
@@ -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,
};
},
});
81 changes: 81 additions & 0 deletions scripts/eslint/rules/no-double-space.ts
Original file line number Diff line number Diff line change
@@ -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,
};
},
});
Loading

0 comments on commit 0059763

Please sign in to comment.