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

Rewrite 'no-mergeable-namespace' to use AST walking instead of document highlights #2105

Merged
merged 3 commits into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 44 additions & 29 deletions src/rules/noMergeableNamespaceRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,48 +32,63 @@ export class Rule extends Lint.Rules.AbstractRule {
};
/* tslint:enable:object-literal-sort-keys */

public static failureStringFactory(identifier: string, locationToMerge: ts.LineAndCharacter): string {
return `Mergeable namespace ${identifier} found. Merge its contents with the namespace on line ${locationToMerge.line}.`;
public static failureStringFactory(name: string, seenBeforeLine: number) {
return `Mergeable namespace '${name}' found. Merge its contents with the namespace on line ${seenBeforeLine}.`;
}

public apply(sourceFile: ts.SourceFile, languageService: ts.LanguageService): Lint.RuleFailure[] {
const noMergeableNamespaceWalker = new NoMergeableNamespaceWalker(sourceFile, this.getOptions(), languageService);
return this.applyWithWalker(noMergeableNamespaceWalker);
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions()));
}
}

class NoMergeableNamespaceWalker extends Lint.RuleWalker {
constructor(sourceFile: ts.SourceFile, options: Lint.IOptions, private languageService: ts.LanguageService) {
super(sourceFile, options);
class Walker extends Lint.RuleWalker {
public visitSourceFile(node: ts.SourceFile) {
this.checkStatements(node.statements);
// All tree-walking handled by 'checkStatements'
}

public visitModuleDeclaration(node: ts.ModuleDeclaration) {
if (Lint.isNodeFlagSet(node, ts.NodeFlags.Namespace)
&& node.name.kind === ts.SyntaxKind.Identifier) {
this.validateReferencesForNamespace((node.name as ts.Identifier).text, node.name.getStart());
}
super.visitModuleDeclaration(node);
}
private checkStatements(statements: ts.Statement[]): void {
const seen = new Map<string, ts.NamespaceDeclaration>();

private validateReferencesForNamespace(name: string, position: number) {
const { fileName } = this.getSourceFile();
const highlights = this.languageService.getDocumentHighlights(fileName, position, [fileName]);
for (const statement of statements) {
if (statement.kind !== ts.SyntaxKind.ModuleDeclaration) {
continue;
}

const { name } = statement as ts.ModuleDeclaration;
if (name.kind === ts.SyntaxKind.Identifier) {
const { text } = name;
const prev = seen.get(text);
if (prev) {
this.addFailureAtNode(name, Rule.failureStringFactory(text, this.getLineOfNode(prev)));
}
seen.set(text, statement as ts.NamespaceDeclaration);
}

if (highlights == null || highlights[0].highlightSpans.length > 1) {
const failureString = Rule.failureStringFactory(name, this.findLocationToMerge(position, highlights[0].highlightSpans));
this.addFailureAt(position, name.length, failureString);
// Recursively check in all module declarations
this.checkModuleDeclaration(statement as ts.ModuleDeclaration);
}
}

private findLocationToMerge(currentPosition: number, highlightSpans: ts.HighlightSpan[]): ts.LineAndCharacter {
const { line } = this.getLineAndCharacterOfPosition(currentPosition);
private checkModuleDeclaration(decl: ts.ModuleDeclaration): void {
const { body } = decl;
if (!body) {
return;
}

for (const span of highlightSpans) {
const lineAndCharacter = this.getLineAndCharacterOfPosition(span.textSpan.start);
if (lineAndCharacter.line !== line) {
return lineAndCharacter;
}
switch (body.kind) {
case ts.SyntaxKind.ModuleBlock:
this.checkStatements(body.statements);
break;
case ts.SyntaxKind.ModuleDeclaration:
this.checkModuleDeclaration(body as ts.ModuleDeclaration);
break;
default:
break;
}
throw new Error("expected more than one highlightSpan");
}

private getLineOfNode(node: ts.Node): number {
return this.getLineAndCharacterOfPosition(node.pos).line;
}
}
22 changes: 0 additions & 22 deletions test/rules/mergeable-namespace/test.ts.lint

This file was deleted.

41 changes: 41 additions & 0 deletions test/rules/no-mergeable-namespace/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// valid case
var foo;
namespace foo{
namespace foo {}
}
declare module buzz {}
module bar {}

// invalid case
namespace a {}
namespace a {}
~ [Mergeable namespace 'a' found. Merge its contents with the namespace on line 6.]

namespace b {
namespace b {}
namespace b {}
~ [Mergeable namespace 'b' found. Merge its contents with the namespace on line 12.]
namespace b {}
~ [Mergeable namespace 'b' found. Merge its contents with the namespace on line 13.]
}

namespace x.y {}
namespace x { namespace y{} }
~ [Mergeable namespace 'x' found. Merge its contents with the namespace on line 16.]

namespace m.n {
namespace l {}
namespace l {}
~ [Mergeable namespace 'l' found. Merge its contents with the namespace on line 21.]
}

module "foo" {
// Different than outer 'a'
namespace a {}

namespace q {}
namespace q {}
~ [Mergeable namespace 'q' found. Merge its contents with the namespace on line 28.]
}

[0]: This namespace has already been declared. Merge the declarations.