diff --git a/.vscode/settings.json b/.vscode/settings.json index 761264b..2a9155d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ }, "typescript.tsdk": "./node_modules/typescript/lib", "tslint.autoFixOnSave": true, - "files.insertFinalNewline": true, + "editor.formatOnSave": true, + "files.insertFinalNewline": true } diff --git a/helpers/getClassMethods.ts b/helpers/getClassMethods.ts index 2264b59..5aa3e73 100644 --- a/helpers/getClassMethods.ts +++ b/helpers/getClassMethods.ts @@ -1,20 +1,20 @@ -import * as ts from 'typescript'; -import { nodeIsKind } from './nodeIsKind'; +import * as ts from 'typescript' +import { nodeIsKind } from './nodeIsKind' export function getClassMethods(node: ts.ClassLikeDeclaration) { if (!node.members) { - return []; + return [] } return node.members.filter(m => { if (nodeIsKind(m, 'MethodDeclaration')) { - return true; + return true } if (nodeIsKind(m, 'PropertyDeclaration')) { - return nodeIsKind(m.initializer, 'ArrowFunction'); + return nodeIsKind(m.initializer, 'ArrowFunction') } - return false; - }) as (ts.MethodDeclaration | ts.PropertyDeclaration)[]; + return false + }) as (ts.MethodDeclaration | ts.PropertyDeclaration)[] } diff --git a/helpers/getJsxAttributes.ts b/helpers/getJsxAttributes.ts index fa6ea4b..28f5b6a 100644 --- a/helpers/getJsxAttributes.ts +++ b/helpers/getJsxAttributes.ts @@ -1,6 +1,10 @@ -import * as ts from 'typescript'; +import * as ts from 'typescript' -export function getJsxAttributes(node: ts.JsxOpeningLikeElement): ts.NodeArray { - return (node.attributes && node.attributes as any).properties || // >= TS 2.3 - (node.attributes as any); // <= TS 2.2 +export function getJsxAttributes( + node: ts.JsxOpeningLikeElement +): ts.NodeArray { + return ( + (node.attributes && (node.attributes as any)).properties || // >= TS 2.3 + (node.attributes as any) // <= TS 2.2 + ) } diff --git a/helpers/getLeadingWhitespace.ts b/helpers/getLeadingWhitespace.ts index 9ea600d..bb82fca 100644 --- a/helpers/getLeadingWhitespace.ts +++ b/helpers/getLeadingWhitespace.ts @@ -1,5 +1,5 @@ -import { Node } from 'typescript'; +import { Node } from 'typescript' export function getLeadingWhitespace(node: Node) { - return node.getFullText().slice(0, node.getStart() - node.getFullStart()); + return node.getFullText().slice(0, node.getStart() - node.getFullStart()) } diff --git a/helpers/nodeIsKind.ts b/helpers/nodeIsKind.ts index 52ed2d0..32f963f 100644 --- a/helpers/nodeIsKind.ts +++ b/helpers/nodeIsKind.ts @@ -1,8 +1,8 @@ -import * as ts from 'typescript'; +import * as ts from 'typescript' export function nodeIsKind( node: ts.Node, - kind: keyof typeof ts.SyntaxKind, + kind: keyof typeof ts.SyntaxKind ): node is T { - return node && node.kind === ts.SyntaxKind[kind]; + return node && node.kind === ts.SyntaxKind[kind] } diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..037d543 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,10 @@ +module.exports = { + printWidth: 90, + tabWidth: 4, + useTabs: true, + semi: false, + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + parser: 'typescript', +} diff --git a/rules/camelCaseLocalFunctionsRule.ts b/rules/camelCaseLocalFunctionsRule.ts index 17b192f..dcc421a 100644 --- a/rules/camelCaseLocalFunctionsRule.ts +++ b/rules/camelCaseLocalFunctionsRule.ts @@ -1,10 +1,10 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } @@ -14,25 +14,20 @@ function walk(ctx: Lint.WalkContext) { nodeIsKind(node, 'CallExpression') && nodeIsKind(node.expression, 'Identifier') ) { - checkFunctionName(ctx, node.expression); + checkFunctionName(ctx, node.expression) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } -const whitelist = [ - 'Array', - 'Boolean', - 'Error', - 'Function', - 'Number', - 'Object', - 'String' -]; +const whitelist = ['Array', 'Boolean', 'Error', 'Function', 'Number', 'Object', 'String'] function checkFunctionName(ctx: Lint.WalkContext, name: ts.Identifier) { - const firstLetter = name.text.charAt(0); - if (firstLetter !== firstLetter.toLowerCase() && whitelist.indexOf(name.text) === -1) { - ctx.addFailureAtNode(name, 'local function names should be camelCase'); + const firstLetter = name.text.charAt(0) + if ( + firstLetter !== firstLetter.toLowerCase() && + whitelist.indexOf(name.text) === -1 + ) { + ctx.addFailureAtNode(name, 'local function names should be camelCase') } } diff --git a/rules/classMethodNewlinesRule.ts b/rules/classMethodNewlinesRule.ts index 03bede4..ec3bee7 100644 --- a/rules/classMethodNewlinesRule.ts +++ b/rules/classMethodNewlinesRule.ts @@ -1,54 +1,53 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { getClassMethods } from '../helpers/getClassMethods'; -import { getLeadingWhitespace } from '../helpers/getLeadingWhitespace'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { getClassMethods } from '../helpers/getClassMethods' +import { getLeadingWhitespace } from '../helpers/getLeadingWhitespace' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } function walk(ctx: Lint.WalkContext) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { - if ( - nodeIsKind(node, 'ClassDeclaration') || - nodeIsKind(node, 'ClassExpression') - ) { - checkClass(ctx, node as ts.ClassLikeDeclaration); + if (nodeIsKind(node, 'ClassDeclaration') || nodeIsKind(node, 'ClassExpression')) { + checkClass(ctx, node as ts.ClassLikeDeclaration) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } function checkClass(ctx: Lint.WalkContext, node: ts.ClassLikeDeclaration) { - const sf = ctx.sourceFile; - const methods = getClassMethods(node); + const sf = ctx.sourceFile + const methods = getClassMethods(node) methods.reduce((previousMethod, method) => { - const leadingWhitespace = getLeadingWhitespace(method); - const newlineCount = leadingWhitespace.match(/\n/g).length; - const hasComments = /\/\/|\/\*\*/g.test(leadingWhitespace); - const isFirstMethod = method === node.members[0]; - const isInOverloadGroup = ( + const leadingWhitespace = getLeadingWhitespace(method) + const newlineCount = leadingWhitespace.match(/\n/g).length + const hasComments = /\/\/|\/\*\*/g.test(leadingWhitespace) + const isFirstMethod = method === node.members[0] + const isInOverloadGroup = method !== previousMethod && method.name.getText(sf) === previousMethod.name.getText(sf) - ); - const expectedNewlines = isFirstMethod || isInOverloadGroup ? 1 : 2; + const expectedNewlines = isFirstMethod || isInOverloadGroup ? 1 : 2 if ( newlineCount < expectedNewlines || (newlineCount > expectedNewlines && !hasComments) ) { - const newLine = leadingWhitespace.match('\r\n') ? '\r\n' : '\n'; + const newLine = leadingWhitespace.match('\r\n') ? '\r\n' : '\n' ctx.addFailureAtNode( method.name, 'class methods should be preceded by an empty line', - Lint.Replacement.appendText(method.getStart(sf) - leadingWhitespace.length, newLine) - ); + Lint.Replacement.appendText( + method.getStart(sf) - leadingWhitespace.length, + newLine + ) + ) } - return method; - }, methods[0]); + return method + }, methods[0]) } diff --git a/rules/declareClassMethodsAfterUseRule.ts b/rules/declareClassMethodsAfterUseRule.ts index 08d9e3b..274a699 100644 --- a/rules/declareClassMethodsAfterUseRule.ts +++ b/rules/declareClassMethodsAfterUseRule.ts @@ -1,18 +1,24 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { getClassMethods } from '../helpers/getClassMethods'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { getClassMethods } from '../helpers/getClassMethods' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new DeclareClassMethodsAfterUseWalker(sourceFile, this.ruleName, this.getOptions())); + return this.applyWithWalker( + new DeclareClassMethodsAfterUseWalker( + sourceFile, + this.ruleName, + this.getOptions() + ) + ) } } class DeclareClassMethodsAfterUseWalker extends Lint.AbstractWalker<{}> { - private currentMethodName: string; - private visitedMethodDeclarations: string[]; - private visitedMethodCalls: string[]; + private currentMethodName: string + private visitedMethodDeclarations: string[] + private visitedMethodCalls: string[] public walk(sourceFile: ts.SourceFile) { const cb = (node: ts.Node): void => { @@ -20,52 +26,57 @@ class DeclareClassMethodsAfterUseWalker extends Lint.AbstractWalker<{}> { nodeIsKind(node, 'ClassDeclaration') || nodeIsKind(node, 'ClassExpression') ) { - this.validate(node as ts.ClassLikeDeclaration); + this.validate(node as ts.ClassLikeDeclaration) } - return ts.forEachChild(node, cb); - }; + return ts.forEachChild(node, cb) + } - ts.forEachChild(sourceFile, cb); + ts.forEachChild(sourceFile, cb) } private validate(node: ts.ClassLikeDeclaration) { - this.visitedMethodDeclarations = []; - this.visitedMethodCalls = []; + this.visitedMethodDeclarations = [] + this.visitedMethodCalls = [] for (const method of getClassMethods(node)) { - this.currentMethodName = method.name.getText(this.getSourceFile()); - this.visitedMethodDeclarations.push(this.currentMethodName); + this.currentMethodName = method.name.getText(this.getSourceFile()) + this.visitedMethodDeclarations.push(this.currentMethodName) ts.forEachChild(method, child => { - this.visitChildren(child); - }); + this.visitChildren(child) + }) } } private visitChildren(node: ts.Node) { ts.forEachChild(node, child => { if (nodeIsKind(child, 'CallExpression')) { - this.visitCallExpressionInMethod(child); + this.visitCallExpressionInMethod(child) } - this.visitChildren(child); - }); + this.visitChildren(child) + }) } private visitCallExpressionInMethod(node: ts.CallExpression) { - if (!this.callExpressionBelongsToThis(node.expression)) { return; } + if (!this.callExpressionBelongsToThis(node.expression)) { + return + } - const propertyExpression = node.expression as ts.PropertyAccessExpression; - const methodName = propertyExpression.name.text; + const propertyExpression = node.expression as ts.PropertyAccessExpression + const methodName = propertyExpression.name.text if (this.methodHasBeenDeclared(methodName)) { if (!this.isRecursion(methodName) && !this.methodHasBeenCalled(methodName)) { - this.addFailureAtNode(propertyExpression, 'declare class methods after use'); + this.addFailureAtNode( + propertyExpression, + 'declare class methods after use' + ) } } else { // declaration needs to come after first use, not all uses. // once we've seen a callsite before a declaration, don't // error on any future callsites for that method - this.visitedMethodCalls.push(methodName); + this.visitedMethodCalls.push(methodName) } } @@ -73,18 +84,18 @@ class DeclareClassMethodsAfterUseWalker extends Lint.AbstractWalker<{}> { return ( nodeIsKind(node, 'PropertyAccessExpression') && nodeIsKind(node.expression, 'ThisKeyword') - ); + ) } private methodHasBeenDeclared(name: string) { - return this.visitedMethodDeclarations.indexOf(name) > -1; + return this.visitedMethodDeclarations.indexOf(name) > -1 } private methodHasBeenCalled(name: string) { - return this.visitedMethodCalls.indexOf(name) > -1; + return this.visitedMethodCalls.indexOf(name) > -1 } private isRecursion(name: string) { - return this.currentMethodName === name; + return this.currentMethodName === name } } diff --git a/rules/jsxAttributeSpacingRule.ts b/rules/jsxAttributeSpacingRule.ts index 255b07f..f58c8d2 100644 --- a/rules/jsxAttributeSpacingRule.ts +++ b/rules/jsxAttributeSpacingRule.ts @@ -1,33 +1,36 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { getJsxAttributes } from '../helpers/getJsxAttributes'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { getJsxAttributes } from '../helpers/getJsxAttributes' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } function walk(ctx: Lint.WalkContext) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (nodeIsKind(node, 'JsxElement')) { - checkAttributes(ctx, getJsxAttributes(node.openingElement)); + checkAttributes(ctx, getJsxAttributes(node.openingElement)) } else if (nodeIsKind(node, 'JsxSelfClosingElement')) { - checkAttributes(ctx, getJsxAttributes(node)); + checkAttributes(ctx, getJsxAttributes(node)) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } -function checkAttributes(ctx: Lint.WalkContext, nodes: ts.NodeArray) { - const sf = ctx.sourceFile; - const nonSpreadAttributes = nodes.filter(n => n.kind === ts.SyntaxKind.JsxAttribute); +function checkAttributes( + ctx: Lint.WalkContext, + nodes: ts.NodeArray +) { + const sf = ctx.sourceFile + const nonSpreadAttributes = nodes.filter(n => n.kind === ts.SyntaxKind.JsxAttribute) for (const attribute of nonSpreadAttributes) { - const [identifier, assignment, initializer] = attribute.getChildren(sf); + const [identifier, assignment, initializer] = attribute.getChildren(sf) if (!initializer) { - continue; + continue } if ( @@ -35,14 +38,22 @@ function checkAttributes(ctx: Lint.WalkContext, nodes: ts.NodeArray) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (nodeIsKind(node, 'JsxExpression')) { - checkExpression(ctx, node); + checkExpression(ctx, node) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } function checkExpression(ctx: Lint.WalkContext, node: ts.JsxExpression) { - const sf = ctx.sourceFile; - const [openingBrace, value, closingBrace] = node.getChildren(sf); + const sf = ctx.sourceFile + const [openingBrace, value, closingBrace] = node.getChildren(sf) if (!value || !nodeIsKind(closingBrace, 'CloseBraceToken')) { - return; + return } if (!isPrecededByValidWhitespace(closingBrace, sf)) { - const braceStart = closingBrace.getStart(sf); - const expressionEnd = value.getEnd(); - const spaceCount = braceStart - expressionEnd; + const braceStart = closingBrace.getStart(sf) + const expressionEnd = value.getEnd() + const spaceCount = braceStart - expressionEnd ctx.addFailureAtNode( closingBrace, `jsx expression should have one space before closing '}'`, spaceCount === 0 ? Lint.Replacement.appendText(expressionEnd, ' ') : Lint.Replacement.deleteText(expressionEnd, spaceCount - 1) - ); + ) } if (!isPrecededByValidWhitespace(value, sf)) { - const braceEnd = openingBrace.getEnd(); - const expressionStart = value.getStart(sf); - const spaceCount = expressionStart - braceEnd; + const braceEnd = openingBrace.getEnd() + const expressionStart = value.getStart(sf) + const spaceCount = expressionStart - braceEnd ctx.addFailureAtNode( openingBrace, `jsx expression should have one space after opening '{'`, spaceCount === 0 ? Lint.Replacement.appendText(braceEnd, ' ') : Lint.Replacement.deleteText(braceEnd, spaceCount - 1) - ); + ) } } @@ -56,5 +56,5 @@ function isPrecededByValidWhitespace(node: ts.Node, sf: ts.SourceFile) { return ( node.getFullStart() === node.getStart(sf) - 1 || /^[\r\n]+/.test(node.getFullText(sf)) - ); + ) } diff --git a/rules/jsxNoBracesForStringAttributesRule.ts b/rules/jsxNoBracesForStringAttributesRule.ts index 1ebe85b..ee617e7 100644 --- a/rules/jsxNoBracesForStringAttributesRule.ts +++ b/rules/jsxNoBracesForStringAttributesRule.ts @@ -1,37 +1,40 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; -import { getJsxAttributes } from '../helpers/getJsxAttributes'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' +import { getJsxAttributes } from '../helpers/getJsxAttributes' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } function walk(ctx: Lint.WalkContext) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (nodeIsKind(node, 'JsxElement')) { - checkAttributes(ctx, getJsxAttributes(node.openingElement)); + checkAttributes(ctx, getJsxAttributes(node.openingElement)) } else if (nodeIsKind(node, 'JsxSelfClosingElement')) { - checkAttributes(ctx, getJsxAttributes(node)); + checkAttributes(ctx, getJsxAttributes(node)) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } -function checkAttributes(ctx: Lint.WalkContext, nodes: ts.NodeArray) { - const sf = ctx.sourceFile; - const nonSpreadAttributes = nodes.filter(n => n.kind === ts.SyntaxKind.JsxAttribute); +function checkAttributes( + ctx: Lint.WalkContext, + nodes: ts.NodeArray +) { + const sf = ctx.sourceFile + const nonSpreadAttributes = nodes.filter(n => n.kind === ts.SyntaxKind.JsxAttribute) for (const attribute of nonSpreadAttributes) { - const { initializer, name } = attribute; + const { initializer, name } = attribute if (!initializer) { - continue; + continue } - const value = initializer.getChildAt(1, sf); - const closeBrace = initializer.getChildAt(2, sf); + const value = initializer.getChildAt(1, sf) + const closeBrace = initializer.getChildAt(2, sf) if ( nodeIsKind(value, 'StringLiteral') && @@ -41,12 +44,14 @@ function checkAttributes(ctx: Lint.WalkContext, nodes: ts.NodeArray) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (nodeIsKind(node, 'JsxElement')) { - check(ctx, node.openingElement); + check(ctx, node.openingElement) } else if (nodeIsKind(node, 'JsxSelfClosingElement')) { - check(ctx, node); + check(ctx, node) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } function check(ctx: Lint.WalkContext, node: ts.JsxOpeningLikeElement) { @@ -26,19 +26,22 @@ function check(ctx: Lint.WalkContext, node: ts.JsxOpeningLikeElement) { const fix = Lint.Replacement.deleteText( token.getFullStart(), token.getStart(ctx.sourceFile) - token.getFullStart() - ); + ) ctx.addFailureAtNode( token, 'closing brackets for jsx elements should not be on newlines', fix - ); + ) } } } function findClosingTokens(node: ts.JsxOpeningLikeElement) { - return node.getChildren().filter(child => - child.kind === ts.SyntaxKind.SlashToken || - child.kind === ts.SyntaxKind.GreaterThanToken - ); + return node + .getChildren() + .filter( + child => + child.kind === ts.SyntaxKind.SlashToken || + child.kind === ts.SyntaxKind.GreaterThanToken + ) } diff --git a/rules/noBracesForSingleLineArrowFunctionsRule.ts b/rules/noBracesForSingleLineArrowFunctionsRule.ts index afa7c24..a61601a 100644 --- a/rules/noBracesForSingleLineArrowFunctionsRule.ts +++ b/rules/noBracesForSingleLineArrowFunctionsRule.ts @@ -1,10 +1,12 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new Walker(sourceFile, this.ruleName, this.getOptions())); + return this.applyWithWalker( + new Walker(sourceFile, this.ruleName, this.getOptions()) + ) } } @@ -12,16 +14,16 @@ class Walker extends Lint.AbstractWalker<{}> { public walk(sourceFile: ts.SourceFile) { const cb = (node: ts.Node): void => { if (nodeIsKind(node, 'ArrowFunction')) { - this.validate(node); + this.validate(node) } - return ts.forEachChild(node, cb); - }; + return ts.forEachChild(node, cb) + } - ts.forEachChild(sourceFile, cb); + ts.forEachChild(sourceFile, cb) } private validate(node: ts.ArrowFunction) { - const { body } = node; + const { body } = node if ( this.functionBodyIsBraced(body) && @@ -32,42 +34,42 @@ class Walker extends Lint.AbstractWalker<{}> { body, 'single-line arrow functions should not be wrapped in braces', Lint.Replacement.replaceNode(body, this.getFixedText(body)) - ); + ) } } private functionBodyIsBraced(node: ts.ConciseBody): node is ts.Block { - return nodeIsKind(node, 'Block'); + return nodeIsKind(node, 'Block') } private functionBodyHasOneStatement(node: ts.Block) { - return node.statements.length === 1; + return node.statements.length === 1 } private functionBodyIsOneLine(node: ts.Block) { - const bodyText = node.getFullText(this.getSourceFile()); - return !/\n/.test(bodyText); + const bodyText = node.getFullText(this.getSourceFile()) + return !/\n/.test(bodyText) } private getFixedText(node: ts.Block) { - const sf = this.getSourceFile(); - const body = node.getChildAt(1, sf); - let result = this.stripSemicolon(body.getText(sf)); + const sf = this.getSourceFile() + const body = node.getChildAt(1, sf) + let result = this.stripSemicolon(body.getText(sf)) - const statement = body.getChildAt(0, sf); + const statement = body.getChildAt(0, sf) if (nodeIsKind(statement, 'ReturnStatement')) { - result = result.replace('return', '').trim(); + result = result.replace('return', '').trim() - const returnExpression = statement.getChildAt(1, sf); + const returnExpression = statement.getChildAt(1, sf) if (nodeIsKind(returnExpression, 'ObjectLiteralExpression')) { - result = `(${result})`; + result = `(${result})` } } - return result; + return result } private stripSemicolon(text: string) { - return text.trim().replace(/;$/, ''); + return text.trim().replace(/;$/, '') } } diff --git a/rules/noPropertyInitializersRule.ts b/rules/noPropertyInitializersRule.ts index c56a018..c7e2f50 100644 --- a/rules/noPropertyInitializersRule.ts +++ b/rules/noPropertyInitializersRule.ts @@ -1,21 +1,23 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new ClassMethodNewlinesWalker(sourceFile, this.getOptions())); + return this.applyWithWalker( + new ClassMethodNewlinesWalker(sourceFile, this.getOptions()) + ) } } class ClassMethodNewlinesWalker extends Lint.RuleWalker { public visitClassDeclaration(node: ts.ClassDeclaration) { - super.visitClassDeclaration(node); - this.validate(node); + super.visitClassDeclaration(node) + this.validate(node) } public visitClassExpression(node: ts.ClassExpression) { - super.visitClassExpression(node); - this.validate(node); + super.visitClassExpression(node) + this.validate(node) } private validate(node: ts.ClassLikeDeclaration) { @@ -27,7 +29,7 @@ class ClassMethodNewlinesWalker extends Lint.RuleWalker { member.name.getWidth(), 'property initializers are nonstandard -- assign in constructor or method' ) - ); + ) } } } diff --git a/rules/noUnnecessaryParensForArrowFunctionArgumentsRule.ts b/rules/noUnnecessaryParensForArrowFunctionArgumentsRule.ts index 96fae00..c338959 100644 --- a/rules/noUnnecessaryParensForArrowFunctionArgumentsRule.ts +++ b/rules/noUnnecessaryParensForArrowFunctionArgumentsRule.ts @@ -1,10 +1,10 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } @@ -14,26 +14,26 @@ function walk(ctx: Lint.WalkContext) { nodeIsKind(node, 'ArrowFunction') && node.parameters.length === 1 ) { - check(ctx, node); + check(ctx, node) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } function check(ctx: Lint.WalkContext, node: ts.ArrowFunction) { - const param = node.parameters[0]; - const hasParens = node.getText(ctx.sourceFile).indexOf('(') === 0; - const hasType = !!param.type; - const isRest = !!param.dotDotDotToken; - const hasDefaultValue = !!param.initializer; + const param = node.parameters[0] + const hasParens = node.getText(ctx.sourceFile).indexOf('(') === 0 + const hasType = !!param.type + const isRest = !!param.dotDotDotToken + const hasDefaultValue = !!param.initializer const isDestructured = param.name.kind === ts.SyntaxKind.ObjectBindingPattern || - param.name.kind === ts.SyntaxKind.ArrayBindingPattern; + param.name.kind === ts.SyntaxKind.ArrayBindingPattern if (hasParens && !(hasType || isDestructured || isRest || hasDefaultValue)) { ctx.addFailureAtNode( param, - 'arrow functions with one argument should not have parentheses around the argument', - ); + 'arrow functions with one argument should not have parentheses around the argument' + ) } } diff --git a/rules/preferEs6ImportsRule.ts b/rules/preferEs6ImportsRule.ts index 2ec4472..bfd5d8d 100644 --- a/rules/preferEs6ImportsRule.ts +++ b/rules/preferEs6ImportsRule.ts @@ -1,25 +1,33 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new PreferEs6ImportsWalker(sourceFile, this.getOptions())); + return this.applyWithWalker( + new PreferEs6ImportsWalker(sourceFile, this.getOptions()) + ) } } class PreferEs6ImportsWalker extends Lint.RuleWalker { public visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration) { - const sf = this.getSourceFile(); - const { moduleReference } = node; - const bannedModuleRequires: string[] = this.getOptions(); + const sf = this.getSourceFile() + const { moduleReference } = node + const bannedModuleRequires: string[] = this.getOptions() if ( - nodeIsKind(moduleReference, 'ExternalModuleReference') && + nodeIsKind( + moduleReference, + 'ExternalModuleReference' + ) && nodeIsKind(moduleReference.expression, 'StringLiteral') ) { - const modulePath = moduleReference.expression.getText(sf); - const moduleName = modulePath.split(/[\/\\]+/).pop().replace(/['"]/g, ''); + const modulePath = moduleReference.expression.getText(sf) + const moduleName = modulePath + .split(/[\/\\]+/) + .pop() + .replace(/['"]/g, '') if (bannedModuleRequires.some(banned => moduleName === banned)) { this.addFailure( @@ -28,10 +36,10 @@ class PreferEs6ImportsWalker extends Lint.RuleWalker { node.getWidth(sf), `use es6 import syntax when importing ${moduleName}` ) - ); + ) } } - super.visitImportEqualsDeclaration(node); + super.visitImportEqualsDeclaration(node) } } diff --git a/rules/preferOrOperatorOverTernaryRule.ts b/rules/preferOrOperatorOverTernaryRule.ts index 384c1ed..e9635e5 100644 --- a/rules/preferOrOperatorOverTernaryRule.ts +++ b/rules/preferOrOperatorOverTernaryRule.ts @@ -1,25 +1,25 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { nodeIsKind } from '../helpers/nodeIsKind'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { nodeIsKind } from '../helpers/nodeIsKind' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithFunction(sourceFile, walk); + return this.applyWithFunction(sourceFile, walk) } } function walk(ctx: Lint.WalkContext) { ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { if (nodeIsKind(node, 'ConditionalExpression')) { - check(ctx, node); + check(ctx, node) } - return ts.forEachChild(node, cb); - }); + return ts.forEachChild(node, cb) + }) } function check(ctx: Lint.WalkContext, node: ts.ConditionalExpression) { - const sf = ctx.sourceFile; - const { condition, whenTrue, questionToken, colonToken } = node; + const sf = ctx.sourceFile + const { condition, whenTrue, questionToken, colonToken } = node if ( condition.kind === ts.SyntaxKind.Identifier && @@ -30,11 +30,11 @@ function check(ctx: Lint.WalkContext, node: ts.ConditionalExpression) { questionToken.getStart(sf), colonToken.getStart(sf) + 1, '||' - ); + ) ctx.addFailureAtNode( whenTrue, `use '||' when first and second operands of ternary are identical`, fix - ); + ) } } diff --git a/rules/reactLifecycleOrderRule.ts b/rules/reactLifecycleOrderRule.ts index 6c77a5a..3b4de1c 100644 --- a/rules/reactLifecycleOrderRule.ts +++ b/rules/reactLifecycleOrderRule.ts @@ -1,10 +1,12 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { getClassMethods } from '../helpers/getClassMethods'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { getClassMethods } from '../helpers/getClassMethods' export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { - return this.applyWithWalker(new ReactLifecyleOrderRule(sourceFile, this.getOptions())); + return this.applyWithWalker( + new ReactLifecyleOrderRule(sourceFile, this.getOptions()) + ) } } @@ -16,44 +18,46 @@ const defaultOrder = [ 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate', - 'componentWillUnmount' -]; + 'componentWillUnmount', +] class ReactLifecyleOrderRule extends Lint.RuleWalker { - private expectedOrder: string[]; + private expectedOrder: string[] public constructor(sourceFile, options) { - super(sourceFile, options); - const orderOptions = this.getOptions(); - this.expectedOrder = orderOptions && orderOptions.length > 0 - ? orderOptions - : defaultOrder; + super(sourceFile, options) + const orderOptions = this.getOptions() + this.expectedOrder = + orderOptions && orderOptions.length > 0 ? orderOptions : defaultOrder } public visitClassDeclaration(node: ts.ClassDeclaration) { - this.validate(node); - super.visitClassDeclaration(node); + this.validate(node) + super.visitClassDeclaration(node) } public visitClassExpression(node: ts.ClassExpression) { - this.validate(node); - super.visitClassExpression(node); + this.validate(node) + super.visitClassExpression(node) } private validate(node: ts.ClassLikeDeclaration) { if (!this.isReactComponent(node)) { - return; + return } - const sf = this.getSourceFile(); - const relevantMethods = getClassMethods(node) - .filter(method => this.expectedOrder.indexOf(method.name.getText(sf)) > -1); + const sf = this.getSourceFile() + const relevantMethods = getClassMethods(node).filter( + method => this.expectedOrder.indexOf(method.name.getText(sf)) > -1 + ) const sortedMethods = relevantMethods.slice().sort((left, right) => { - const leftName = left.name.getText(sf); - const rightName = right.name.getText(sf); - return this.expectedOrder.indexOf(leftName) > this.expectedOrder.indexOf(rightName) ? 1 : -1; - }); + const leftName = left.name.getText(sf) + const rightName = right.name.getText(sf) + const leftIndex = this.expectedOrder.indexOf(leftName) + const rightIndex = this.expectedOrder.indexOf(rightName) + return leftIndex > rightIndex ? 1 : -1 + }) relevantMethods.forEach((method, index) => { if (sortedMethods[index] !== method) { @@ -61,28 +65,32 @@ class ReactLifecyleOrderRule extends Lint.RuleWalker { this.createFailure( method.name.getStart(sf), method.name.getWidth(sf), - `expected React lifecyle method '${sortedMethods[index].name.getText(sf)}'` + `expected React lifecyle method '${sortedMethods[ + index + ].name.getText(sf)}'` ) - ); + ) } - }); + }) } private isReactComponent(node: ts.ClassLikeDeclaration) { if (!node.heritageClauses || node.heritageClauses.length !== 1) { - return false; + return false } - const ancestor = node.heritageClauses[0].types[0]; + const ancestor = node.heritageClauses[0].types[0] if (!ancestor) { - return false; + return false } - return [ - 'React.Component', - 'React.PureComponent', - 'Component', - 'PureComponent' - ].indexOf(ancestor.getText(this.getSourceFile())) > -1; + return ( + [ + 'React.Component', + 'React.PureComponent', + 'Component', + 'PureComponent', + ].indexOf(ancestor.getText(this.getSourceFile())) > -1 + ) } } diff --git a/rules/sortImportsRule.ts b/rules/sortImportsRule.ts index 66aa66b..f6fcbe5 100644 --- a/rules/sortImportsRule.ts +++ b/rules/sortImportsRule.ts @@ -1,109 +1,112 @@ -import * as Lint from 'tslint/lib'; -import * as ts from 'typescript'; -import { graceful as detectNewline } from 'detect-newline'; +import * as Lint from 'tslint/lib' +import * as ts from 'typescript' +import { graceful as detectNewline } from 'detect-newline' interface Options { - whitespaceInsensitive: boolean; + whitespaceInsensitive: boolean } export class Rule extends Lint.Rules.AbstractRule { public apply(sourceFile: ts.SourceFile) { const options: Options = { - whitespaceInsensitive: this.getOptions().ruleArguments.indexOf('whitespace-insensitive') > -1 - }; + whitespaceInsensitive: + this.getOptions().ruleArguments.indexOf('whitespace-insensitive') > -1, + } - return this.applyWithWalker(new Walker(sourceFile, this.ruleName, options)); + return this.applyWithWalker(new Walker(sourceFile, this.ruleName, options)) } } class Walker extends Lint.AbstractWalker { public walk(sf: ts.SourceFile) { for (const importGroup of this.getImportGroups(sf)) { - const sortableLinesToImports = new Map(); + const sortableLinesToImports = new Map() const unsortedLines = importGroup.map(importDeclaration => { // opening brace is below alphanumeric characters char-code-wise, but want named imports above defaults // + comes directly after *, so swap with that - let sortableLine = importDeclaration.getText(sf) + let sortableLine = importDeclaration + .getText(sf) .toLowerCase() - .replace('import {', 'import +'); + .replace('import {', 'import +') if (this.options.whitespaceInsensitive) { - sortableLine = this.normalizeAllWhitespace(sortableLine); + sortableLine = this.normalizeAllWhitespace(sortableLine) } - sortableLinesToImports.set(sortableLine, importDeclaration); - return sortableLine; - }); + sortableLinesToImports.set(sortableLine, importDeclaration) + return sortableLine + }) - const sortedLines = unsortedLines.slice().sort(); + const sortedLines = unsortedLines.slice().sort() for (let i = 0; i < unsortedLines.length; i += 1) { if ( unsortedLines[i] !== sortedLines[i] && - !this.isSideEffectImport(sortableLinesToImports.get(unsortedLines[i])) && + !this.isSideEffectImport( + sortableLinesToImports.get(unsortedLines[i]) + ) && !this.isSideEffectImport(sortableLinesToImports.get(sortedLines[i])) ) { const expectedImportIndex = this.findIndex( unsortedLines, line => line === sortedLines[i] - ); - const expectedImport = importGroup[expectedImportIndex]; - const actualImport = importGroup[i]; + ) + const expectedImport = importGroup[expectedImportIndex] + const actualImport = importGroup[i] const message = this.normalizeAllWhitespace( this.getFailureMessage(expectedImport, actualImport) - ); + ) const sortedImports = sortedLines.map(line => { - return sortableLinesToImports.get(line).getFullText(sf).trim(); - }); + return sortableLinesToImports + .get(line) + .getFullText(sf) + .trim() + }) - const groupStart = importGroup[0].getStart(sf); - const groupEnd = importGroup[importGroup.length - 1].getEnd(); - const newline = detectNewline(sf.getFullText()); + const groupStart = importGroup[0].getStart(sf) + const groupEnd = importGroup[importGroup.length - 1].getEnd() + const newline = detectNewline(sf.getFullText()) const fix = Lint.Replacement.replaceFromTo( groupStart, groupEnd, sortedImports.join(newline) - ); + ) // work around some kind of multiline error span bug in tslint rule testing const failureNode = /\n/.test(actualImport.getText(sf).trim()) ? actualImport.getFirstToken() - : actualImport; - - this.addFailureAtNode( - failureNode, - message, - fix - ); - break; + : actualImport + + this.addFailureAtNode(failureNode, message, fix) + break } } } } private getImportGroups(sourceFile: ts.SourceFile) { - let breakGroup = true; - const importGroups: ts.ImportDeclaration[][] = [[]]; + let breakGroup = true + const importGroups: ts.ImportDeclaration[][] = [[]] for (const statement of sourceFile.statements) { if (this.isImportStatement(statement)) { - importGroups[importGroups.length - 1].push(statement); - breakGroup = true; + importGroups[importGroups.length - 1].push(statement) + breakGroup = true } else if (breakGroup) { - importGroups.push([]); - breakGroup = false; + importGroups.push([]) + breakGroup = false } } - return importGroups.filter(group => group.length > 0); + return importGroups.filter(group => group.length > 0) } private findIndex(array: string[], predicate: (str: string) => boolean) { for (let i = 0; i < array.length; i += 1) { if (predicate(array[i])) { - return i; + return i } } } @@ -112,42 +115,42 @@ class Walker extends Lint.AbstractWalker { return ( node.kind === ts.SyntaxKind.ImportDeclaration || this.isImportRequireStatement(node) - ); + ) } - private getFailureMessage(expectedImport: ts.ImportDeclaration, actualImport: ts.ImportDeclaration) { - const expected = this.getImportBindingName(expectedImport); - const actual = this.getImportBindingName(actualImport); - return `out-of-order imports: expected '${expected}' but saw '${actual}'`; + private getFailureMessage( + expectedImport: ts.ImportDeclaration, + actualImport: ts.ImportDeclaration + ) { + const expected = this.getImportBindingName(expectedImport) + const actual = this.getImportBindingName(actualImport) + return `out-of-order imports: expected '${expected}' but saw '${actual}'` } - private getImportBindingName(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration) { - const sf = this.getSourceFile(); + private getImportBindingName( + node: ts.ImportDeclaration | ts.ImportEqualsDeclaration + ) { + const sf = this.getSourceFile() if (this.isImportRequireStatement(node)) { - return node.name.getText(sf); + return node.name.getText(sf) } else { if (node.importClause) { - return node.importClause.getText(sf); + return node.importClause.getText(sf) } - return node.moduleSpecifier.getText(sf); + return node.moduleSpecifier.getText(sf) } } private isImportRequireStatement(node: ts.Node): node is ts.ImportEqualsDeclaration { - return node.kind === ts.SyntaxKind.ImportEqualsDeclaration; + return node.kind === ts.SyntaxKind.ImportEqualsDeclaration } private isSideEffectImport(node: ts.ImportDeclaration) { - return ( - !this.isImportRequireStatement(node) && - !node.importClause - ); + return !this.isImportRequireStatement(node) && !node.importClause } private normalizeAllWhitespace(content: string) { - return content - .replace(/[\s]+/g, ' ') - .replace(', }', ' }'); + return content.replace(/[\s]+/g, ' ').replace(', }', ' }') } }