Created
June 20, 2016 21:02
-
-
Save danvk/8ddaddaf291769b9bcfa8d4504742150 to your computer and use it in GitHub Desktop.
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
import * as ts from "typescript"; | |
import * as Lint from "tslint/lib/lint"; | |
export class Rule extends Lint.Rules.AbstractRule { | |
public static NAMED_IMPORTS_UNORDERED = "Named imports must be alphabetized."; | |
public static IMPORT_SOURCES_UNORDERED = "Import sources within a block must be alphabetized."; | |
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | |
const orderedImportsWalker = new OrderedImportsWalker(sourceFile, this.getOptions()); | |
return this.applyWithWalker(orderedImportsWalker); | |
} | |
} | |
// Are the nodes sorted according to the text they contain? | |
function findUnsortedPair(xs: ts.Node[]): [ts.Node, ts.Node] { | |
for (let i = 1; i < xs.length; i++) { | |
if (xs[i].getText().toLowerCase() < xs[i - 1].getText().toLowerCase()) { | |
return [xs[i - 1], xs[i]]; | |
} | |
} | |
return null; | |
} | |
// Returns './utils' given the tree for, e.g. "import * as utils from './utils';" | |
function findFromSource(node: ts.Node): string { | |
const num = node.getChildCount(); | |
for (let i = 0; i < num - 1; i++) { | |
const child = node.getChildAt(i); | |
if (child.kind !== ts.SyntaxKind.FromKeyword) continue; | |
const next = node.getChildAt(i + 1); | |
if (next.kind !== ts.SyntaxKind.StringLiteral) { | |
throw new Error('Unable to parse import: ' + node.getFullText()); | |
} | |
return next.getText(); | |
} | |
return null; | |
} | |
class OrderedImportsWalker extends Lint.RuleWalker { | |
// This gets reset after every blank line. | |
lastImportSource: string = null; | |
// e.g. "import Foo from './foo';" | |
public visitImportDeclaration(node: ts.ImportDeclaration) { | |
const source = findFromSource(node); | |
if (this.lastImportSource && source < this.lastImportSource) { | |
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), | |
Rule.IMPORT_SOURCES_UNORDERED)); | |
} | |
this.lastImportSource = source; | |
super.visitImportDeclaration(node); | |
} | |
// This is the "{A, B, C}" of "import {A, B, C} from './foo';". | |
// We need to make sure they're alphabetized. | |
public visitNamedImports(node: ts.NamedImports) { | |
const names = node.getChildAt(1); // Three children: "{", "A, B, C" and "}" | |
const imports: ts.Node[] = []; | |
for (const child of names.getChildren()) { | |
if (child.kind === ts.SyntaxKind.ImportSpecifier) { | |
imports.push(child); | |
} | |
} | |
const pair = findUnsortedPair(imports); | |
if (pair) { | |
const [a, b] = pair; | |
this.addFailure( | |
this.createFailure( | |
a.getStart(), | |
b.getEnd() - a.getStart(), | |
Rule.NAMED_IMPORTS_UNORDERED)); | |
} | |
super.visitNamedImports(node); | |
} | |
// Check for a blank line, in which case we should reset the import ordering. | |
public visitNode(node: ts.Node) { | |
const prefixLength = node.getStart() - node.getFullStart(); | |
const prefix = node.getFullText().slice(0, prefixLength); | |
if (prefix.indexOf('\n\n') >= 0) { | |
this.lastImportSource = null; | |
} | |
super.visitNode(node); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment