code-server-2/rules/src/noBlockPaddingRule.ts

90 lines
3.0 KiB
TypeScript

import * as ts from "typescript";
import * as Lint from "tslint";
/**
* Rule for disallowing blank lines around the content of blocks.
*/
export class Rule extends Lint.Rules.AbstractRule {
public static BEFORE_FAILURE_STRING = "Blocks must not start with blank lines";
public static AFTER_FAILURE_STRING = "Blocks must not end with blank lines";
/**
* Apply the rule.
*/
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoBlockPaddingWalker(sourceFile, this.getOptions()));
}
}
/**
* Walker for checking block padding.
*/
class NoBlockPaddingWalker extends Lint.RuleWalker {
/**
* Apply this rule to interfaces.
*/
public visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void {
this.visitBlockNode(node);
super.visitInterfaceDeclaration(node);
}
/**
* Apply this rule to classes.
*/
public visitClassDeclaration(node: ts.ClassDeclaration): void {
this.visitBlockNode(node);
super.visitClassDeclaration(node);
}
/**
* Add failures to blank lines surrounding a block's content.
*/
private visitBlockNode(node: ts.ClassDeclaration | ts.InterfaceDeclaration): void {
const sourceFile = node.getSourceFile();
const children = node.getChildren();
const openBraceIndex = children.findIndex((n) => n.kind === ts.SyntaxKind.OpenBraceToken);
if (openBraceIndex !== -1) {
const nextToken = children[openBraceIndex + 1];
if (nextToken) {
const startLine = this.getStartIncludingComments(sourceFile, nextToken);
const openBraceToken = children[openBraceIndex];
if (ts.getLineAndCharacterOfPosition(sourceFile, openBraceToken.getEnd()).line + 1 < startLine) {
this.addFailureAt(openBraceToken.getEnd(), openBraceToken.getEnd(), Rule.BEFORE_FAILURE_STRING);
}
}
}
const closeBraceIndex = children.findIndex((n) => n.kind === ts.SyntaxKind.CloseBraceToken);
if (closeBraceIndex >= 2) {
const previousToken = children[closeBraceIndex - 1];
if (previousToken) {
let endLine = ts.getLineAndCharacterOfPosition(sourceFile, previousToken.getEnd()).line;
const closeBraceToken = children[closeBraceIndex];
if (this.getStartIncludingComments(sourceFile, closeBraceToken) > endLine + 1) {
this.addFailureAt(closeBraceToken.getStart(), closeBraceToken.getStart(), Rule.AFTER_FAILURE_STRING);
}
}
}
}
/**
* getStart() doesn't account for comments while this does.
*/
private getStartIncludingComments(sourceFile: ts.SourceFile, node: ts.Node): number {
// This gets the line the node starts on without counting comments.
let startLine = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart()).line;
// Adjust the start line for the comments.
const comments = ts.getLeadingCommentRanges(sourceFile.text, node.pos) || [];
comments.forEach((c) => {
const commentStartLine = ts.getLineAndCharacterOfPosition(sourceFile, c.pos).line;
if (commentStartLine < startLine) {
startLine = commentStartLine;
}
});
return startLine;
}
}