add eslint

This commit is contained in:
Alexander T
2019-06-13 13:37:14 +03:00
parent 74c6bc1f85
commit 55b8a38d50
18 changed files with 145 additions and 734 deletions

View File

@@ -1,97 +0,0 @@
import * as Lint from "tslint";
import chalk from "chalk";
import { sep } from "path";
function groupBy<T>(array: ReadonlyArray<T> | undefined, getGroupId: (elem: T, index: number) => number | string): T[][] {
if (!array) {
return [];
}
const groupIdToGroup: { [index: string]: T[] } = {};
let result: T[][] | undefined; // Compacted array for return value
for (let index = 0; index < array.length; index++) {
const value = array[index];
const key = getGroupId(value, index);
if (groupIdToGroup[key]) {
groupIdToGroup[key].push(value);
}
else {
const newGroup = [value];
groupIdToGroup[key] = newGroup;
if (!result) {
result = [newGroup];
}
else {
result.push(newGroup);
}
}
}
return result || [];
}
function max<T>(array: ReadonlyArray<T> | undefined, selector: (elem: T) => number): number {
if (!array) {
return 0;
}
let max = 0;
for (const item of array) {
const scalar = selector(item);
if (scalar > max) {
max = scalar;
}
}
return max;
}
function getLink(failure: Lint.RuleFailure, color: boolean): string {
const lineAndCharacter = failure.getStartPosition().getLineAndCharacter();
const sev = failure.getRuleSeverity().toUpperCase();
let path = failure.getFileName();
// Most autolinks only become clickable if they contain a slash in some way; so we make a top level file into a relative path here
if (path.indexOf("/") === -1 && path.indexOf("\\") === -1) {
path = `.${sep}${path}`;
}
return `${color ? (sev === "WARNING" ? chalk.blue(sev) : chalk.red(sev)) : sev}: ${path}:${lineAndCharacter.line + 1}:${lineAndCharacter.character + 1}`;
}
function getLinkMaxSize(failures: Lint.RuleFailure[]): number {
return max(failures, f => getLink(f, /*color*/ false).length);
}
function getNameMaxSize(failures: Lint.RuleFailure[]): number {
return max(failures, f => f.getRuleName().length);
}
function pad(str: string, visiblelen: number, len: number) {
if (visiblelen >= len) return str;
const count = len - visiblelen;
for (let i = 0; i < count; i++) {
str += " ";
}
return str;
}
export class Formatter extends Lint.Formatters.AbstractFormatter {
public static metadata: Lint.IFormatterMetadata = {
formatterName: "autolinkableStylish",
description: "Human-readable formatter which creates stylish messages with autolinkable filepaths.",
descriptionDetails: Lint.Utils.dedent`
Colorized output grouped by file, with autolinkable filepaths containing line and column information
`,
sample: Lint.Utils.dedent`
src/myFile.ts
ERROR: src/myFile.ts:1:14 semicolon Missing semicolon`,
consumer: "human"
};
public format(failures: Lint.RuleFailure[]): string {
return groupBy(failures, f => f.getFileName()).map(group => {
const currentFile = group[0].getFileName();
const linkMaxSize = getLinkMaxSize(group);
const nameMaxSize = getNameMaxSize(group);
return `
${currentFile}
${group.map(f => `${pad(getLink(f, /*color*/ true), getLink(f, /*color*/ false).length, linkMaxSize)} ${chalk.grey(pad(f.getRuleName(), f.getRuleName().length, nameMaxSize))} ${chalk.yellow(f.getFailure())}`).join("\n")}`;
}).join("\n");
}
}

View File

@@ -1,93 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, ctx => walk(ctx));
}
}
function walk(ctx: Lint.WalkContext<void>): void {
const { sourceFile } = ctx;
ts.forEachChild(sourceFile, function recur(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.CallExpression) {
checkCall(node as ts.CallExpression);
}
ts.forEachChild(node, recur);
});
function checkCall(node: ts.CallExpression): void {
if (!shouldIgnoreCalledExpression(node.expression)) {
for (const arg of node.arguments) {
checkArg(arg);
}
}
}
/** Skip certain function/method names whose parameter names are not informative. */
function shouldIgnoreCalledExpression(expression: ts.Expression): boolean {
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const methodName = (expression as ts.PropertyAccessExpression).name.text;
if (methodName.startsWith("set") || methodName.startsWith("assert")) {
return true;
}
switch (methodName) {
case "apply":
case "call":
case "equal":
case "fail":
case "isTrue":
case "output":
case "stringify":
return true;
}
}
else if (expression.kind === ts.SyntaxKind.Identifier) {
const functionName = (expression as ts.Identifier).text;
if (functionName.startsWith("set") || functionName.startsWith("assert")) {
return true;
}
switch (functionName) {
case "contains":
case "createAnonymousType":
case "createImportSpecifier":
case "createProperty":
case "createSignature":
case "resolveName":
return true;
}
}
return false;
}
function checkArg(arg: ts.Expression): void {
if (!isTrivia(arg)) {
return;
}
const ranges = ts.getTrailingCommentRanges(sourceFile.text, arg.pos) || ts.getLeadingCommentRanges(sourceFile.text, arg.pos);
if (ranges === undefined || ranges.length !== 1 || ranges[0].kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
ctx.addFailureAtNode(arg, "Tag argument with parameter name");
return;
}
const range = ranges[0];
const argStart = arg.getStart(sourceFile);
if (range.end + 1 !== argStart && sourceFile.text.slice(range.end, argStart).indexOf("\n") === -1) {
ctx.addFailureAtNode(arg, "There should be 1 space between an argument and its comment.");
}
}
function isTrivia(arg: ts.Expression): boolean {
switch (arg.kind) {
case ts.SyntaxKind.TrueKeyword:
case ts.SyntaxKind.FalseKeyword:
case ts.SyntaxKind.NullKeyword:
return true;
case ts.SyntaxKind.Identifier:
return (arg as ts.Identifier).originalKeywordKind === ts.SyntaxKind.UndefinedKeyword;
default:
return false;
}
}
}

View File

@@ -1,45 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, ctx => walk(ctx));
}
}
function walk(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, function recur(node) {
if (ts.isCallExpression(node)) {
checkCall(node);
}
ts.forEachChild(node, recur);
});
function checkCall(node: ts.CallExpression) {
if (!isDebugAssert(node.expression) || node.arguments.length < 2) {
return;
}
const message = node.arguments[1];
if (!ts.isStringLiteral(message)) {
ctx.addFailureAtNode(message, "Second argument to 'Debug.assert' should be a string literal.");
}
if (node.arguments.length < 3) {
return;
}
const message2 = node.arguments[2];
if (!ts.isStringLiteral(message2) && !ts.isArrowFunction(message2)) {
ctx.addFailureAtNode(message, "Third argument to 'Debug.assert' should be a string literal or arrow function.");
}
}
function isDebugAssert(expr: ts.Node): boolean {
return ts.isPropertyAccessExpression(expr) && isName(expr.expression, "Debug") && isName(expr.name, "assert");
}
function isName(expr: ts.Node, text: string): boolean {
return ts.isIdentifier(expr) && expr.text === text;
}
}

View File

@@ -1,67 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
const OPTION_CATCH = "check-catch";
const OPTION_ELSE = "check-else";
export class Rule extends Lint.Rules.AbstractRule {
public static CATCH_FAILURE_STRING = "'catch' should not be on the same line as the preceeding block's curly brace";
public static ELSE_FAILURE_STRING = "'else' should not be on the same line as the preceeding block's curly brace";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const options = this.getOptions().ruleArguments;
const checkCatch = options.indexOf(OPTION_CATCH) !== -1;
const checkElse = options.indexOf(OPTION_ELSE) !== -1;
return this.applyWithFunction(sourceFile, ctx => walk(ctx, checkCatch, checkElse));
}
}
function walk(ctx: Lint.WalkContext<void>, checkCatch: boolean, checkElse: boolean): void {
const { sourceFile } = ctx;
ts.forEachChild(sourceFile, function recur(node) {
switch (node.kind) {
case ts.SyntaxKind.IfStatement:
checkIf(node as ts.IfStatement);
break;
case ts.SyntaxKind.TryStatement:
checkTry(node as ts.TryStatement);
break;
}
ts.forEachChild(node, recur);
});
function checkIf(node: ts.IfStatement): void {
const { thenStatement, elseStatement } = node;
if (!elseStatement) {
return;
}
// find the else keyword
const elseKeyword = getFirstChildOfKind(node, ts.SyntaxKind.ElseKeyword);
if (checkElse && !!elseKeyword) {
const thenStatementEndLoc = sourceFile.getLineAndCharacterOfPosition(thenStatement.getEnd());
const elseKeywordLoc = sourceFile.getLineAndCharacterOfPosition(elseKeyword.getStart(sourceFile));
if (thenStatementEndLoc.line === elseKeywordLoc.line) {
ctx.addFailureAtNode(elseKeyword, Rule.ELSE_FAILURE_STRING);
}
}
}
function checkTry({ tryBlock, catchClause }: ts.TryStatement): void {
if (!checkCatch || !catchClause) {
return;
}
const tryClosingBrace = tryBlock.getLastToken(sourceFile)!;
const catchKeyword = catchClause.getFirstToken(sourceFile)!;
const tryClosingBraceLoc = sourceFile.getLineAndCharacterOfPosition(tryClosingBrace.getEnd());
const catchKeywordLoc = sourceFile.getLineAndCharacterOfPosition(catchKeyword.getStart(sourceFile));
if (tryClosingBraceLoc.line === catchKeywordLoc.line) {
ctx.addFailureAtNode(catchKeyword, Rule.CATCH_FAILURE_STRING);
}
}
}
function getFirstChildOfKind(node: ts.Node, kind: ts.SyntaxKind) {
return node.getChildren().filter((child) => child.kind === kind)[0];
}

View File

@@ -1,16 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "This file has a BOM.";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
if (ctx.sourceFile.text[0] === "\ufeff") {
ctx.addFailure(0, 1, Rule.FAILURE_STRING);
}
}

View File

@@ -1,54 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
const { sourceFile } = ctx;
const lines = sourceFile.text.split("\n");
const strings = getLiterals(sourceFile);
lines.forEach((line, idx) => {
// Skip indentation.
const firstNonSpace = /\S/.exec(line);
if (firstNonSpace === null) {
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 = /[^/*. ] [^-!/= ]/g;
rgx.lastIndex = firstNonSpace.index;
const doubleSpace = rgx.exec(line);
// Also allow to align comments after `@param`
if (doubleSpace !== null && !line.includes("@param")) {
const pos = lines.slice(0, idx).reduce((len, line) => len + 1 + line.length, 0) + doubleSpace.index;
if (!strings.some(s => s.getStart() <= pos && s.end > pos)) {
ctx.addFailureAt(pos + 1, 2, "Use only one space.");
}
}
});
}
function getLiterals(sourceFile: ts.SourceFile): ReadonlyArray<ts.Node> {
const out: ts.Node[] = [];
sourceFile.forEachChild(function cb(node) {
switch (node.kind) {
case ts.SyntaxKind.StringLiteral:
case ts.SyntaxKind.TemplateHead:
case ts.SyntaxKind.TemplateMiddle:
case ts.SyntaxKind.TemplateTail:
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
case ts.SyntaxKind.RegularExpressionLiteral:
out.push(node);
}
node.forEachChild(cb);
});
return out;
}

View File

@@ -1,19 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "Don't use the 'in' keyword - use 'hasProperty' to check for key presence instead";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, recur);
function recur(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.InKeyword && node.parent.kind === ts.SyntaxKind.BinaryExpression) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
}
}
}

View File

@@ -1,55 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static POSTFIX_FAILURE_STRING = "Don't use '++' or '--' postfix operators outside statements or for loops.";
public static PREFIX_FAILURE_STRING = "Don't use '++' or '--' prefix operators.";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, recur);
function recur(node: ts.Node): void {
switch (node.kind) {
case ts.SyntaxKind.PrefixUnaryExpression:
const { operator } = node as ts.PrefixUnaryExpression;
if (operator === ts.SyntaxKind.PlusPlusToken || operator === ts.SyntaxKind.MinusMinusToken) {
check(node as ts.PrefixUnaryExpression);
}
break;
case ts.SyntaxKind.PostfixUnaryExpression:
check(node as ts.PostfixUnaryExpression);
break;
}
}
function check(node: ts.UnaryExpression): void {
if (!isAllowedLocation(node.parent)) {
ctx.addFailureAtNode(node, Rule.POSTFIX_FAILURE_STRING);
}
}
}
function isAllowedLocation(node: ts.Node): boolean {
switch (node.kind) {
// Can be a statement
case ts.SyntaxKind.ExpressionStatement:
return true;
// Can be directly in a for-statement
case ts.SyntaxKind.ForStatement:
return true;
// Can be in a comma operator in a for statement (`for (let a = 0, b = 10; a < b; a++, b--)`)
case ts.SyntaxKind.BinaryExpression:
return (node as ts.BinaryExpression).operatorToken.kind === ts.SyntaxKind.CommaToken &&
node.parent.kind === ts.SyntaxKind.ForStatement;
default:
return false;
}
}

View File

@@ -1,25 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static TRAILING_FAILURE_STRING = "Excess trailing whitespace found around type assertion.";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
ts.forEachChild(ctx.sourceFile, recur);
function recur(node: ts.Node) {
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
const refined = node as ts.TypeAssertion;
const leftSideWhitespaceStart = refined.type.getEnd() + 1;
const rightSideWhitespaceEnd = refined.expression.getStart();
if (leftSideWhitespaceStart !== rightSideWhitespaceEnd) {
ctx.addFailure(leftSideWhitespaceStart, rightSideWhitespaceEnd, Rule.TRAILING_FAILURE_STRING);
}
}
ts.forEachChild(node, recur);
}
}

View File

@@ -1,44 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static LEADING_FAILURE_STRING = "No leading whitespace found on single-line object literal.";
public static TRAILING_FAILURE_STRING = "No trailing whitespace found on single-line object literal.";
public static LEADING_EXCESS_FAILURE_STRING = "Excess leading whitespace found on single-line object literal.";
public static TRAILING_EXCESS_FAILURE_STRING = "Excess trailing whitespace found on single-line object literal.";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
const { sourceFile } = ctx;
ts.forEachChild(sourceFile, recur);
function recur(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.ObjectLiteralExpression) {
check(node as ts.ObjectLiteralExpression);
}
ts.forEachChild(node, recur);
}
function check(node: ts.ObjectLiteralExpression): void {
const text = node.getText(sourceFile);
if (!text.match(/^{[^\n]+}$/g)) {
return;
}
if (text.charAt(1) !== " ") {
ctx.addFailureAtNode(node, Rule.LEADING_FAILURE_STRING);
}
if (text.charAt(2) === " ") {
ctx.addFailureAt(node.pos + 2, 1, Rule.LEADING_EXCESS_FAILURE_STRING);
}
if (text.charAt(text.length - 2) !== " ") {
ctx.addFailureAtNode(node, Rule.TRAILING_FAILURE_STRING);
}
if (text.charAt(text.length - 3) === " ") {
ctx.addFailureAt(node.pos + node.getWidth() - 3, 1, Rule.TRAILING_EXCESS_FAILURE_STRING);
}
}
}

View File

@@ -1,30 +0,0 @@
import * as Lint from "tslint/lib";
import * as ts from "typescript";
export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "The '|' and '&' operators must be surrounded by spaces";
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>): void {
const { sourceFile } = ctx;
sourceFile.forEachChild(function cb(node: ts.Node): void {
if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) {
check(node);
}
node.forEachChild(cb);
});
function check(node: ts.UnionTypeNode | ts.IntersectionTypeNode): void {
const list = node.getChildren().find(child => child.kind === ts.SyntaxKind.SyntaxList)!;
for (const child of list.getChildren()) {
if ((child.kind === ts.SyntaxKind.BarToken || child.kind === ts.SyntaxKind.AmpersandToken)
&& (/\S/.test(sourceFile.text[child.getStart(sourceFile) - 1]) || /\S/.test(sourceFile.text[child.end]))) {
ctx.addFailureAtNode(child, Rule.FAILURE_STRING);
}
}
}
}

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"lib": ["es6"],
"sourceMap": false,
"declaration": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"module": "commonjs",
"outDir": "../../built/local/tslint",
"baseUrl": "../..",
"types": ["node"],
"paths": {
"typescript": ["lib/typescript.d.ts"]
}
}
}