tslint-lines-between-class-members
Advanced tools
Comparing version
// Unopinionated extensible tslint configuration | ||
// Loads rules for extending packages, but does not enable any | ||
module.exports = { | ||
rulesDirectory: './' | ||
rulesDirectory: '.' | ||
}; |
{ | ||
"name": "tslint-lines-between-class-members", | ||
"version": "1.3.2", | ||
"version": "1.3.3", | ||
"description": "Custom rule for TSLint to enforce blank lines between class methods - achieves a similar thing to lines-between-class-members in ESLint", | ||
@@ -9,3 +9,3 @@ "scripts": { | ||
"lint": "tslint --config tslint.json --exclude node_modules **/*.ts", | ||
"test": "ava-ts src/**/*.spec.ts --verbose", | ||
"test": "ava-ts src/**/test/**/*.spec.ts --verbose", | ||
"test:watch": "npm test -- --watch" | ||
@@ -30,5 +30,5 @@ }, | ||
"ts-node": "^6.0.0", | ||
"tslint": "^5.9.1", | ||
"typescript": "2.3.4" | ||
"typescript": "3.4.5", | ||
"tslint": "5.15.0" | ||
} | ||
} | ||
} |
import * as Lint from 'tslint'; | ||
import * as ts from 'typescript'; | ||
import { | ||
isPreviousLineClassDec, | ||
isClassMethod, | ||
isPrevLineOpeningBrace, | ||
getNodeLineStart, | ||
getLineDifference | ||
} from './utils'; | ||
export class Rule extends Lint.Rules.AbstractRule { | ||
static readonly metadata: Lint.IRuleMetadata = { | ||
ruleName: 'lines-between-class-members', | ||
type: 'style', | ||
description: 'Forces all classes to have at least one, or an exact number of, lines between class members', | ||
options: 'boolean', | ||
optionsDescription: 'Exact number of lines that should be between class members', | ||
rationale: 'Ensures consistency between classes', | ||
typescriptOnly: true, | ||
}; | ||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
@@ -35,108 +52,17 @@ return this.applyWithWalker( | ||
private validate(node: ts.FunctionLikeDeclaration) { | ||
const arePrevLinesBlank = this.arePreviousLinesBlank(node, this.getSourceFile()); | ||
const isPrevLineClassDec = this.isPreviousLineClassDec(node, this.getSourceFile()); | ||
const isPrevLineOpeningBrace = this.isPrevLineOpeningBrace(node, this.getSourceFile()); | ||
const isClassMethod = this.isClassMethod(node); | ||
if (!arePrevLinesBlank && !isPrevLineClassDec && !isPrevLineOpeningBrace && isClassMethod) { | ||
this.onRuleLintFail(node); | ||
} | ||
} | ||
/* Calculate difference between lines above class member and desired lines */ | ||
this.difference = getLineDifference(node, this.getSourceFile(), this.getOptions()); | ||
/** | ||
* Tests lines above the method are blank | ||
* Tests exact number of lines if option has been specified, or just checks for one new line if not | ||
* A line is considered blank if it is an empty new line or if there are only whitespace characters present | ||
*/ | ||
private arePreviousLinesBlank(node: ts.FunctionLikeDeclaration, sourceFile: ts.SourceFile): boolean { | ||
const options = this.getOptions(); | ||
this.difference = 0; | ||
if (options.length > 0) { | ||
// if user has specified the number of new lines they want between their methods | ||
// we need to check there are exactly that many blank lines | ||
/* Ignore method immediately following class declaration */ | ||
const prevLineClassDec = isPreviousLineClassDec(node, this.getSourceFile()); | ||
const prevLineOpeningBrace = isPrevLineOpeningBrace(node, this.getSourceFile()); | ||
const numLinesOption = options[0]; | ||
// check for invalid num lines option | ||
if (!/^[0-9]+$/.test(numLinesOption)) { | ||
return false; | ||
} | ||
/* Ignore methods inside object literals */ | ||
const classMethod = isClassMethod(node); | ||
// check each previous line is blank for num lines specified | ||
let i; | ||
for (i = 0; i < numLinesOption; i++) { | ||
if (!this.isLineBlank(this.getPrevLinesText(node, sourceFile, i + 1))) { | ||
this.difference = numLinesOption - i; | ||
return false; | ||
} | ||
} | ||
// then check that the line before is NOT blank | ||
// we count how many lines it takes to get to a non-blank one so we can fix properly | ||
let isLineBlank = this.isLineBlank(this.getPrevLinesText(node, sourceFile, i + 1)); | ||
if (isLineBlank) { | ||
while (isLineBlank) { | ||
i++; | ||
this.difference--; | ||
isLineBlank = this.isLineBlank(this.getPrevLinesText(node, sourceFile, i + 1)); | ||
} | ||
return false; | ||
} | ||
return true; | ||
} else { | ||
// if user has not specified the number of blank lines, we just want to check there | ||
// is at least one | ||
return this.isLineBlank(this.getPrevLinesText(node, sourceFile)); | ||
if (this.difference !== 0 && !prevLineClassDec && !prevLineOpeningBrace && classMethod) { | ||
this.onRuleLintFail(node); | ||
} | ||
} | ||
private isLineBlank(line: string) { | ||
return line.length === 0 || !(/\S/.test(line)); | ||
} | ||
/** | ||
* Tests whether the previous line is the class declaration | ||
* We do not want to enforce a new line between class declaration and constructor (or other first method) | ||
*/ | ||
private isPreviousLineClassDec(node: ts.FunctionLikeDeclaration, sourceFile: ts.SourceFile): boolean { | ||
const prevLine = this.getPrevLinesText(node, sourceFile); | ||
return /\b(class|implements|extends)\b\s+[A-Za-z0-9]+/.test(prevLine); | ||
} | ||
/** | ||
* Tests whether the previous line is the opening brace | ||
* We do not want to enforce a newline after opening brace for the class declaration | ||
*/ | ||
private isPrevLineOpeningBrace(node: ts.FunctionLikeDeclaration, sourceFile: ts.SourceFile): boolean { | ||
const prevLine = this.getPrevLinesText(node, sourceFile); | ||
return prevLine.trim() === '{'; | ||
} | ||
/** | ||
* Tests whether method is within a class (as opposed to within an object literal) | ||
*/ | ||
private isClassMethod(node: ts.FunctionLikeDeclaration): boolean { | ||
const parentType = node.parent && node.parent.kind; | ||
return parentType === ts.SyntaxKind.ClassDeclaration; | ||
} | ||
/** | ||
* Gets the text content of a line above the method | ||
* Any documenting comments are ignored and we start from the first line above those | ||
* If lineIndex is passed, it will get the text of the nth line above the method | ||
*/ | ||
private getPrevLinesText(node: ts.FunctionLikeDeclaration, sourceFile: ts.SourceFile, lineIndex = 1): string { | ||
let pos = node.getStart(); | ||
const comments = ts.getLeadingCommentRanges(sourceFile.text, node.pos) || []; | ||
if (comments.length > 0) { | ||
pos = comments[0].pos; | ||
} | ||
const lineStartPositions = <any>sourceFile.getLineStarts(); | ||
const startPosIdx = lineStartPositions.findIndex((startPos, idx) => | ||
startPos > pos || idx === lineStartPositions.length - 1 | ||
) - lineIndex; | ||
return sourceFile.text.substring(lineStartPositions[startPosIdx - 1], lineStartPositions[startPosIdx] - 1); | ||
} | ||
private onRuleLintFail(node: ts.FunctionLikeDeclaration) { | ||
@@ -160,10 +86,21 @@ let start = node.getStart(); | ||
const numLinesOption = options[0]; | ||
const sourceFile = this.getSourceFile(); | ||
// find whitespace identation of current line, node start is start of text but any | ||
// identation needs to be calculated here | ||
let whitespace = ''; | ||
const lineStart = getNodeLineStart(node, sourceFile); | ||
if (lineStart !== start) { | ||
whitespace = Array(start - lineStart).fill(' ').join(''); | ||
width += start - lineStart; | ||
start = lineStart; | ||
} | ||
if (numLinesOption == null) { | ||
errorMessage = 'must have at least one new line between class methods'; | ||
replacement = new Lint.Replacement(start, width, `\n ${text}`); | ||
replacement = new Lint.Replacement(start, width, `\n${whitespace}${text}`); | ||
} else if (!/^[0-9]+$/.test(numLinesOption)) { | ||
errorMessage = `invalid value provided for num lines configuration - ${numLinesOption}, see docs for how to configure`; | ||
} else { | ||
errorMessage = `must have ${numLinesOption} new line(s) between class methods, see docs for how to configure`; | ||
errorMessage = `must have ${numLinesOption} new line(s) between class methods`; | ||
@@ -173,3 +110,3 @@ if (this.difference > 0) { | ||
const newLines = Array(this.difference).fill('\n').join(''); | ||
replacement = new Lint.Replacement(start, width, `${newLines} ${text}`); | ||
replacement = new Lint.Replacement(start, width, `${newLines}${whitespace}${text}`); | ||
} else if (this.difference < 0) { | ||
@@ -183,4 +120,5 @@ // too many lines delete some | ||
start = lineStartPositions[startPosIdx + this.difference]; | ||
width += lineStartPositions[startPosIdx] - start + 2; | ||
replacement = new Lint.Replacement(start, width, ` ${text}`); | ||
width += lineStartPositions[startPosIdx] - start; | ||
replacement = new Lint.Replacement(start, width, `${whitespace}${text}`); | ||
} | ||
@@ -187,0 +125,0 @@ } |
@@ -6,2 +6,2 @@ { | ||
} | ||
} | ||
} |
class NoLine { | ||
constructor() {} | ||
method() {} | ||
} |
// this is a comment | ||
class NoLineAndCommentAboveClass { | ||
constructor() {} | ||
method() {} | ||
} |
class NoLineAndCommentBetweenVarAndConstructor { | ||
myVar: string = 'my nice string'; | ||
// this is a constructor | ||
@@ -6,0 +6,0 @@ constructor() {} |
@@ -6,3 +6,3 @@ class NoLineAndCommentBetweenVarAndMethod { | ||
myVar: string = 'my nice string'; | ||
/** | ||
@@ -14,5 +14,5 @@ * Documenting this method with block comment | ||
myNum: number = 24; | ||
// Documenting this method with single line comment | ||
method() {} | ||
} |
class NoLineAndMethodComment { | ||
constructor() {} | ||
/** | ||
@@ -9,5 +9,5 @@ * Documenting this method with block comment | ||
method() {} | ||
// Documenting this method with single line comment | ||
anotherMethod() {} | ||
} |
class NoLineBetweenVarAndConstructor { | ||
myVar: string = 'my nice string'; | ||
constructor() {} | ||
@@ -6,0 +6,0 @@ |
@@ -6,4 +6,4 @@ class NoLineBetweenVarAndMethod { | ||
myVar: string = 'my nice string'; | ||
method() {} | ||
} |
class NoNewLines { | ||
constructor() {} | ||
method() {} | ||
} |
class TwoNewLines { | ||
constructor() {} | ||
@@ -4,0 +4,0 @@ |
class OneNewLine { | ||
constructor() {} | ||
method() {} | ||
} |
class ThreeNewLines { | ||
constructor() {} | ||
@@ -5,0 +5,0 @@ |
class TwoNewLines { | ||
constructor() {} | ||
@@ -5,0 +5,0 @@ |
class OneNewLine { | ||
constructor() {} | ||
@@ -4,0 +4,0 @@ |
class ThreeNewLines { | ||
constructor() {} | ||
@@ -6,0 +6,0 @@ |
class OneNewLine { | ||
constructor() {} | ||
@@ -4,0 +4,0 @@ |
@@ -17,4 +17,4 @@ { | ||
"exclude": [ | ||
"**/*.spec.ts" | ||
"src/test/**/*" | ||
] | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
57
5.56%128305
-5.4%799
-16.34%1
Infinity%