Skip to content

Commit

Permalink
[new-fixer] file-header rule (palantir#3475)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkillian authored and adidahiya committed Nov 27, 2017
1 parent cbdcfb5 commit 497c3e1
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 10 deletions.
50 changes: 44 additions & 6 deletions src/rules/fileHeaderRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,26 @@ export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
ruleName: "file-header",
description: "Enforces a certain header comment for all files, matched by a regular expression.",
optionsDescription: "Regular expression to match the header.",
optionsDescription: Lint.Utils.dedent`
The first option, which is mandatory, is a regular expression that all headers should match.
The second argument, which is optional, is a string that should be inserted as a header comment
if fixing is enabled and no header that matches the first argument is found.`,
options: {
type: "string",
type: "array",
items: [
{
type: "string",
},
{
type: "string",
},
],
additionalItems: false,
minLength: 1,
maxLength: 2,
},
optionExamples: [[true, "Copyright \\d{4}"]],
optionExamples: [[true, "Copyright \\d{4}", "Copyright 2017"]],
hasFix: true,
type: "style",
typescriptOnly: false,
};
Expand All @@ -37,19 +52,42 @@ export class Rule extends Lint.Rules.AbstractRule {

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const { text } = sourceFile;
const headerFormat = new RegExp(this.ruleArguments[0] as string);
const textToInsert = this.ruleArguments[1] as string | undefined;

// ignore shebang if it exists
let offset = text.startsWith("#!") ? text.indexOf("\n") : 0;
// returns the text of the first comment or undefined
const commentText = ts.forEachLeadingCommentRange(
text,
offset,
(pos, end, kind) => text.substring(pos + 2, kind === ts.SyntaxKind.SingleLineCommentTrivia ? end : end - 2));
if (commentText === undefined || !new RegExp(this.ruleArguments[0] as string).test(commentText)) {
if (offset !== 0) {

if (commentText === undefined || !headerFormat.test(commentText)) {
const isErrorAtStart = offset === 0;
if (!isErrorAtStart) {
++offset; // show warning in next line after shebang
}
return [new Lint.RuleFailure(sourceFile, offset, offset, Rule.FAILURE_STRING, this.ruleName)];
const leadingNewlines = isErrorAtStart ? 0 : 1;
const trailingNewlines = isErrorAtStart ? 2 : 1;

const fix = textToInsert !== undefined
? Lint.Replacement.appendText(offset, this.createComment(sourceFile, textToInsert, leadingNewlines, trailingNewlines))
: undefined;
return [new Lint.RuleFailure(sourceFile, offset, offset, Rule.FAILURE_STRING, this.ruleName, fix)];
}
return [];
}

private createComment(sourceFile: ts.SourceFile, commentText: string, leadingNewlines = 1, trailingNewlines = 1) {
const maybeCarriageReturn = sourceFile.text[sourceFile.getLineEndOfPosition(0)] === "\r" ? "\r" : "";
const lineEnding = `${maybeCarriageReturn}\n`;
return lineEnding.repeat(leadingNewlines) + [
"/*",
// split on both types of line endings in case users just typed "\n" in their configs
// but are working in files with \r\n line endings
...commentText.split(/\r?\n/g).map((line) => ` * ${line}`),
" */",
].join(lineEnding) + lineEnding.repeat(trailingNewlines);
}
}
21 changes: 21 additions & 0 deletions test/rules/file-header/bad-shebang/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node

/*
* Good header 2
*/

/*
* Bad header 3
*/

export class A {
public x = 1;

public B() {
return 2;
}
}

/*
* Good header 4
*/
2 changes: 1 addition & 1 deletion test/rules/file-header/bad-shebang/tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rules": {
"file-header": [true, "Good header \\d"]
"file-header": [true, "Good header \\d", "Good header 2"]
}
}
17 changes: 17 additions & 0 deletions test/rules/file-header/bad-single-line/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Good header 2
*/

// Bad header 1

export class A {
public x = 1;

public B() {
return 2;
}
}

/*
* Good header 2
*/
2 changes: 1 addition & 1 deletion test/rules/file-header/bad-single-line/tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rules": {
"file-header": [true, "Good header \\d"]
"file-header": [true, "Good header \\d", "Good header 2"]
}
}
21 changes: 21 additions & 0 deletions test/rules/file-header/bad-use-strict/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Good header 2
*/

"use strict";

/*
* Bad header 5
*/

export class A {
public x = 1;

public B() {
return 2;
}
}

/*
* Good header 6
*/
2 changes: 1 addition & 1 deletion test/rules/file-header/bad-use-strict/tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rules": {
"file-header": [true, "Good header \\d"]
"file-header": [true, "Good header \\d", "Good header 2"]
}
}
19 changes: 19 additions & 0 deletions test/rules/file-header/bad/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Good header 2
*/

/*
* Bad header 1
*/

export class A {
public x = 1;

public B() {
return 2;
}
}

/*
* Good header 2
*/
2 changes: 1 addition & 1 deletion test/rules/file-header/bad/tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rules": {
"file-header": [true, "Good header \\d"]
"file-header": [true, "Good header \\d", "Good header 2"]
}
}
12 changes: 12 additions & 0 deletions test/rules/file-header/good/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

/*
* Good header 1
*/

export class A {
public x = 1;

public B() {
return 2;
}
}

0 comments on commit 497c3e1

Please sign in to comment.