-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
392 additions
and
330 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ts.MethodDeclaration>(m, 'MethodDeclaration')) { | ||
return true; | ||
return true | ||
} | ||
|
||
if (nodeIsKind<ts.PropertyDeclaration>(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)[] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
import * as ts from 'typescript'; | ||
import * as ts from 'typescript' | ||
|
||
export function getJsxAttributes(node: ts.JsxOpeningLikeElement): ts.NodeArray<ts.JsxAttribute> { | ||
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<ts.JsxAttribute> { | ||
return ( | ||
(node.attributes && (node.attributes as any)).properties || // >= TS 2.3 | ||
(node.attributes as any) // <= TS 2.2 | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import * as ts from 'typescript'; | ||
import * as ts from 'typescript' | ||
|
||
export function nodeIsKind<T extends ts.Node>( | ||
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module.exports = { | ||
printWidth: 90, | ||
tabWidth: 4, | ||
useTabs: true, | ||
semi: false, | ||
singleQuote: true, | ||
trailingComma: 'es5', | ||
bracketSpacing: true, | ||
parser: 'typescript', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<void>) { | ||
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<void>, 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]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,101 @@ | ||
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 => { | ||
if ( | ||
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<ts.CallExpression>(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) | ||
} | ||
} | ||
|
||
private callExpressionBelongsToThis(node: ts.Expression) { | ||
return ( | ||
nodeIsKind<ts.PropertyAccessExpression>(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 | ||
} | ||
} |
Oops, something went wrong.