You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

tslint-lines-between-class-members

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tslint-lines-between-class-members - npm Package Compare versions

Comparing version

to
1.3.3

src/test/fails.spec.ts

2

index.js
// 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