Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Rewrite to always use a single fix
Browse files Browse the repository at this point in the history
And preserve all statements.
  • Loading branch information
eriktim committed Jul 28, 2017
1 parent f9b5d0b commit cb2aa6e
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 82 deletions.
145 changes: 67 additions & 78 deletions src/rules/groupedImportsRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ export class Rule extends Lint.Rules.AbstractRule {
};
/* tslint:enable:object-literal-sort-keys */

public static IMPORT_SOURCES_SEPARATED = "Import sources within a group must not be separated by blank lines";
public static IMPORT_SOURCES_NOT_SEPARATED =
"Import sources of different groups must be separated by a single blank line";
public static IMPORT_SOURCES_ORDER =
public static GROUPED_IMPORTS =
"Import sources of different groups must be sorted by: libraries, parent directories, current directory";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
Expand All @@ -52,49 +49,45 @@ enum ImportStatementType {
}

interface ImportStatement {
statement: ts.Statement;
statement: ts.ImportDeclaration;
type: ImportStatementType;
lineStart: number;
lineEnd: number;
}

class Walker extends Lint.AbstractWalker<Lint.IOptions> {
private lastImportStatement: ImportStatement;
private newLine: string;
private allImportsFix: boolean;
private previousImportStatement: ImportStatement | undefined;

public walk(sourceFile: ts.SourceFile): void {
this.newLine = this.getEofChar(sourceFile);
sourceFile.statements
const importsStatements = sourceFile.statements
.filter(isImportDeclaration)
.forEach(this.checkStatement);
}

private getEofChar(sourceFile: ts.SourceFile): string {
const lineEnd = sourceFile.getLineEndOfPosition(0);
let newLine;
if (lineEnd > 0) {
if (lineEnd > 1 && sourceFile.text[lineEnd - 1] === "\r") {
newLine = "\r\n";
} else if (sourceFile.text[lineEnd] === "\n") {
newLine = "\n";
}
.map(this.toImportStatement);
const firstFailure = importsStatements.find(this.isBadlyPositioned);
if (firstFailure != null) {
const fix = this.createFix(importsStatements);
this.addFailureAtNode(firstFailure.statement, Rule.GROUPED_IMPORTS, fix);
}
return newLine == null ? ts.sys.newLine : newLine;
}

private checkStatement = (statement: ts.ImportDeclaration): void => {
if (this.allImportsFix) {
return;
}
const importStatement = this.toImportStatement(statement);
if (this.lastImportStatement != null) {
this.checkForFailure(importStatement);
private isBadlyPositioned = (importStatement: ImportStatement): boolean => {
if (this.previousImportStatement != null) {
if (importStatement.type === this.previousImportStatement.type) {
if (importStatement.lineStart !== this.previousImportStatement.lineEnd + 1) {
return true;
}
} else {
if (importStatement.type < this.previousImportStatement.type) {
return true;
} else if (importStatement.lineStart !== this.previousImportStatement.lineEnd + 2) {
return true;
}
}
}
this.lastImportStatement = importStatement;
this.previousImportStatement = importStatement;
return false;
}

private toImportStatement(statement: ts.ImportDeclaration): ImportStatement {
private toImportStatement = (statement: ts.ImportDeclaration): ImportStatement => {
return {
lineEnd: this.sourceFile.getLineAndCharacterOfPosition(statement.getEnd()).line,
lineStart: this.sourceFile.getLineAndCharacterOfPosition(statement.getStart()).line,
Expand Down Expand Up @@ -123,60 +116,56 @@ class Walker extends Lint.AbstractWalker<Lint.IOptions> {
return "";
}

private checkForFailure(importStatement: ImportStatement): void {
if (importStatement.type === this.lastImportStatement.type) {
if (importStatement.lineStart !== this.lastImportStatement.lineEnd + 1) {
this.addSeparatedImportsFailure(importStatement);
}
} else {
if (importStatement.type < this.lastImportStatement.type) {
this.addIncorrectlyOrderedImportsFailure(importStatement);
} else if (importStatement.lineStart !== this.lastImportStatement.lineEnd + 2) {
this.addNotSeparatedImportsFailure(importStatement);
}
}
private createFix(importStatements: ImportStatement[]): Lint.Fix {
const newLine = this.getEofChar(this.sourceFile);
const imports = this.getOrderedImports(importStatements);
const addition = Lint.Replacement.appendText(0, imports.join(newLine));
const deletions = importStatements.map((imp) => {
const [start, end] = this.getRangeIncludingWhitespace(imp.statement);
return Lint.Replacement.deleteFromTo(start, end);
});
return [...deletions, addition];
}

private addSeparatedImportsFailure(importStatement: ImportStatement): void {
const text = [this.lastImportStatement, importStatement]
.map((st) => st.statement.getText())
.join(this.newLine);
const replacement = Lint.Replacement.replaceFromTo(
this.lastImportStatement.statement.getStart(), importStatement.statement.getEnd(), text);
this.addFailureAtNode(importStatement.statement, Rule.IMPORT_SOURCES_SEPARATED, replacement);
private getOrderedImports(importStatements: ImportStatement[]): string[] {
const libs = importStatements.filter((imp) => imp.type === ImportStatementType.LIBRARY_IMPORT);
const parent = importStatements.filter((imp) => imp.type === ImportStatementType.PARENT_DIRECTORY_IMPORT);
const current = importStatements.filter((imp) => imp.type === ImportStatementType.CURRENT_DIRECTORY_IMPORT);
return [libs, parent, current].reduce((arr, imps) => {
if (imps.length == 0) {
return arr;
}
return arr.concat(imps.map((imp) => imp.statement.getText()), "");
}, [] as string[]).concat("");
}

private addIncorrectlyOrderedImportsFailure(importStatement: ImportStatement): void {
this.allImportsFix = true;
this.failures.length = 0;
this.addFailureAtNode(importStatement.statement, Rule.IMPORT_SOURCES_ORDER, this.getAllImportsFix());
private getRangeIncludingWhitespace(statement: ts.ImportDeclaration): [number, number] {
const text = this.sourceFile.text;
let start = statement.getStart();
while (this.isWhiteSpace(text[start - 1])) {
start--;
}
let end = statement.getEnd();
while (this.isWhiteSpace(text[end + 1])) {
end++;
}
return [start, end];
}

private addNotSeparatedImportsFailure(importStatement: ImportStatement): void {
const replacement = Lint.Replacement.replaceFromTo(this.lastImportStatement.statement.getEnd(),
importStatement.statement.getStart(), this.newLine + this.newLine);
this.addFailureAtNode(importStatement.statement, Rule.IMPORT_SOURCES_NOT_SEPARATED, replacement);
private isWhiteSpace(char: string | undefined): boolean {
return char === undefined ? false : ts.isWhiteSpaceLike(char.charCodeAt(0));
}

private getAllImportsFix(): Lint.Fix {
const importStatements = this.sourceFile.statements.filter(isImportDeclaration);
const libs = importStatements.filter(
(st) => this.getImportStatementType(st) === ImportStatementType.LIBRARY_IMPORT);
const parent = importStatements.filter(
(st) => this.getImportStatementType(st) === ImportStatementType.PARENT_DIRECTORY_IMPORT);
const current = importStatements.filter(
(st) => this.getImportStatementType(st) === ImportStatementType.CURRENT_DIRECTORY_IMPORT);
let imports: string[] = [];
[libs, parent, current].forEach((statements) => {
if (statements.length > 0) {
imports = imports.concat(statements.map((st) => st.getText()));
imports.push("");
private getEofChar(sourceFile: ts.SourceFile): string {
const lineEnd = sourceFile.getLineEndOfPosition(0);
let newLine;
if (lineEnd > 0) {
if (lineEnd > 1 && sourceFile.text[lineEnd - 1] === "\r") {
newLine = "\r\n";
} else if (sourceFile.text[lineEnd] === "\n") {
newLine = "\n";
}
});
return Lint.Replacement.replaceFromTo(
importStatements[0].getStart(),
importStatements[importStatements.length - 1].getEnd(),
imports.join(this.newLine),
);
}
return newLine == null ? ts.sys.newLine : newLine;
}
}
2 changes: 2 additions & 0 deletions test/rules/grouped-imports/different-groups.ts.fix
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {foo} from 'foo';

import {bar} from '../bar';


2 changes: 1 addition & 1 deletion test/rules/grouped-imports/different-groups.ts.lint
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import {foo} from 'foo';
import {bar} from '../bar';
~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be separated by a single blank line]
~~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be sorted by: libraries, parent directories, current directory]
1 change: 1 addition & 0 deletions test/rules/grouped-imports/same-group.ts.fix
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import {foo} from 'foo';
import {bar} from 'bar';

2 changes: 1 addition & 1 deletion test/rules/grouped-imports/same-group.ts.lint
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {foo} from 'foo';

import {bar} from 'bar';
~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must not be separated by blank lines]
~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be sorted by: libraries, parent directories, current directory]
2 changes: 2 additions & 0 deletions test/rules/grouped-imports/single-line.ts.fix
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ import bar from "bar";
import foo from "foo";

import {baz} from "./baz";


3 changes: 1 addition & 2 deletions test/rules/grouped-imports/single-line.ts.lint
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import bar from "bar";import foo from "foo";import {baz} from "./baz";
~~~~~~~~~~~~~~~~~~~~~~ [Import sources within a group must not be separated by blank lines]
~~~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be separated by a single blank line]
~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be sorted by: libraries, parent directories, current directory]
13 changes: 13 additions & 0 deletions test/rules/grouped-imports/statements.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {foo} from 'foo';

import {bar} from '../bar';

import './baz';

let bar2 = false;

const one = 1; // some comment

const two = 2;
// another comment
let q = 'a';
13 changes: 13 additions & 0 deletions test/rules/grouped-imports/statements.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {bar} from '../bar';let bar2 = false;

const one = 1;

import {foo} from 'foo'; // some comment
~~~~~~~~~~~~~~~~~~~~~~~~ [Import sources of different groups must be sorted by: libraries, parent directories, current directory]

const two = 2;
// another comment

import './baz';

let q = 'a';

0 comments on commit cb2aa6e

Please sign in to comment.