Skip to content

Commit

Permalink
Add support for RexExp pattern in comment-format rule exceptions (fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
IllusionMH committed Dec 11, 2016
1 parent 5df075a commit 3d6ccef
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 37 deletions.
106 changes: 83 additions & 23 deletions src/rules/commentFormatRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
import * as ts from "typescript";

import * as Lint from "../index";
import { escapeRegExp } from "../utils";

interface IExceptionsObject {
ignoreWords?: string[];
ignorePattern?: string[];
}

type ExceptionsRegExp = RegExp | null;

const OPTION_SPACE = "check-space";
const OPTION_LOWERCASE = "check-lowercase";
Expand All @@ -35,29 +43,60 @@ export class Rule extends Lint.Rules.AbstractRule {
* \`"check-space"\` requires that all single-line comments must begin with a space, as in \`// comment\`
* note that comments starting with \`///\` are also allowed, for things such as \`///<reference>\`
* \`"check-lowercase"\` requires that the first non-whitespace character of a comment must be lowercase, if applicable.
* \`"check-uppercase"\` requires that the first non-whitespace character of a comment must be uppercase, if applicable.`,
* \`"check-uppercase"\` requires that the first non-whitespace character of a comment must be uppercase, if applicable.
Exceptions to \`"check-lowercase"\` or \`"check-uppercase"\` can be managed with object that may be passed as last argument.
One of two options can be provided in this object:
* \`"ignoreWords"\` - array of strings - words that will be ignored at the beginning of the comment.
* \`"ignorePattern"\` - string - RegExp pattern that will be ignored at the beginning of the comment.
`,
options: {
type: "array",
items: {
oneOf: [{
type: "string",
enum: ["check-space", "check-lowercase", "check-uppercase"],
}, {
type: "array",
items: { type: "string" },
}],
anyOf: [
{
type: "string",
enum: [
"check-space",
"check-lowercase",
"check-uppercase",
],
},
{
type: "object",
properties: {
ignoreWords: {
type: "array",
items: {
type: "string",
},
},
ignorePattern: {
type: "string",
},
},
minProperties: 1,
maxProperties: 1,
},
],
},
minLength: 1,
maxLength: 4,
},
optionExamples: ['[true, "check-space", "check-uppercase"], [true, "check-lowercase", ["TODO", "HACK"]]'],
optionExamples: [
'[true, "check-space", "check-uppercase"]',
'[true, "check-lowercase", {"ignoreWords": ["TODO", "HACK"]}]',
'[true, "check-lowercase", {"ignorePattern": "STD\\w{2,3}\\b"}]',
],
type: "style",
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */

public static LOWERCASE_FAILURE = "comment must start with lowercase letter or word from exceptions list";
public static UPPERCASE_FAILURE = "comment must start with uppercase letter or word from exceptions list";
public static LOWERCASE_FAILURE = "comment must start with lowercase letter, word from exceptions list or exceptions pattern";
public static UPPERCASE_FAILURE = "comment must start with uppercase letter, word from exceptions list or exceptions pattern";
public static LEADING_SPACE_FAILURE = "comment must start with a space";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
Expand All @@ -66,20 +105,12 @@ export class Rule extends Lint.Rules.AbstractRule {
}

class CommentWalker extends Lint.SkippableTokenAwareRuleWalker {
private exceptionsPattern: RegExp | null;
private exceptionsRegExp: ExceptionsRegExp;

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);

const optionsList = this.getOptions() as Array<string | string[]>;
const possibleExceptions = optionsList[optionsList.length - 1];
if (Array.isArray(possibleExceptions)) {
const wordsPattern = possibleExceptions.map(String).join("|");
// regex is "start of string"//"any amount of whitespace"("any word from exception list") followed by non alphanumeric character
this.exceptionsPattern = new RegExp(`^\\/\\/\\s*(${wordsPattern})\\W`);
} else {
this.exceptionsPattern = null;
}
this.exceptionsRegExp = this.composeExceptionsRegExp();
}

public visitSourceFile(node: ts.SourceFile) {
Expand Down Expand Up @@ -120,11 +151,40 @@ class CommentWalker extends Lint.SkippableTokenAwareRuleWalker {
}

private startsWithException(commentText: string): boolean {
if (this.exceptionsPattern == null) {
if (this.exceptionsRegExp == null) {
return false;
}

return this.exceptionsPattern.test(commentText);
return this.exceptionsRegExp.test(commentText);
}

private composeExceptionsRegExp(): ExceptionsRegExp {
const optionsList = this.getOptions() as Array<string | IExceptionsObject>;
const exceptionsObject = optionsList[optionsList.length - 1];

// early return if last element is string instead of exceptions object
if (typeof exceptionsObject === "string" || !exceptionsObject) {
return null;
}

if (exceptionsObject.ignorePattern) {
// regex is "start of string"//"any amount of whitespace" followed by user provided ignore pattern
return new RegExp(`^//\\s*(${exceptionsObject.ignorePattern})`);
}

if (exceptionsObject.ignoreWords) {
// Converts all exceptions values to strings, trim whitespace, escapes RegExp special characters and combines into alternation
const wordsPattern = exceptionsObject.ignoreWords
.map(String)
.map((str) => str.trim())
.map(escapeRegExp)
.join("|");

// regex is "start of string"//"any amount of whitespace"("any word from ignore list") followed by non alphanumeric character
return new RegExp(`^//\\s*(${wordsPattern})\\b`);
}

return null;
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,10 @@ export function stripComments(content: string): string {
});
return result;
};

/**
* Escapes all special characters in RegExp pattern to avoid broken regular expressions and ensure proper matches
*/
export function escapeRegExp(re: string): string {
return re.replace(/[.+*?|^$\[]{}()\\]/g, "$&");
}
38 changes: 38 additions & 0 deletions test/rules/comment-format/exceptions-pattern/test.js.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Clazz { // This comment is correct
/* block comment
* adada
*/
public funcxion() { // this comment has a lowercase letter starting it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [upper]
//this comment is on its own line, and starts with a lowercase _and_ no space
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [upper]
console.log("test"); //This comment has no space
}
/// <reference or something>
}

//#region test
//#endregion

`${location.protocol}//${location.hostname}`

// tslint should show error here
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [upper]

// tslint: not a rule flag
~~~~~~~~~~~~~~~~~~~~~~~~ [upper]

class Invalid {}

// tslint:disable-next-line:no-unused-expression
class Valid {}

// todo write more tests
~~~~~~~~~~~~~~~~~~~~~~ [upper]

// STDIN for input
// STDOUT for output
// stderr for errors


[upper]: comment must start with uppercase letter, word from exceptions list or exceptions pattern
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ class Clazz { // this comment is correct
const unusedVar = 'unneeded value';

// TODO: Write more tests

~~~~~~~~~~~~~~~~~~~~~~~ [lower]
// HACKING is not an exception
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [lower]

[lower]: comment must start with lowercase letter or word from exceptions list
// STDIN for input
// STDOUT for output
// stderr for errors



[lower]: comment must start with lowercase letter, word from exceptions list or exceptions pattern
[space]: comment must start with a space
8 changes: 8 additions & 0 deletions test/rules/comment-format/exceptions-pattern/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rules": {
"comment-format": [true, "check-space", "check-lowercase", {"ignorePattern": "STD\\w{2,3}"}]
},
"jsRules": {
"comment-format": [true, "check-uppercase", {"ignorePattern": "std(in|out|err)\\b"}]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ class Valid {}

// todo write more tests

[upper]: comment must start with uppercase letter or word from exceptions list
// STDIN for input
// STDOUT for output
// stderr for errors
~~~~~~~~~~~~~~~~~~ [upper]

[upper]: comment must start with uppercase letter, word from exceptions list or exceptions pattern
37 changes: 37 additions & 0 deletions test/rules/comment-format/exceptions-words/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Clazz { // this comment is correct
/* block comment
* adada
*/
public funcxion() { // This comment has a capital letter starting it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [lower]
//This comment is on its own line, and starts with a capital _and_ no space
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [lower]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [space]
console.log("test"); //this comment has no space
~~~~~~~~~~~~~~~~~~~~~~~~~ [space]
}
/// <reference or something>
}

//#region test
//#endregion

`${location.protocol}//${location.hostname}`

//noinspection JSUnusedGlobalSymbols
const unusedVar = 'unneeded value';

// TODO: Write more tests

// HACKING is not an exception
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [lower]

// STDIN for input
~~~~~~~~~~~~~~~~ [lower]
// STDOUT for output
~~~~~~~~~~~~~~~~~~ [lower]
// stderr for errors


[lower]: comment must start with lowercase letter, word from exceptions list or exceptions pattern
[space]: comment must start with a space
8 changes: 8 additions & 0 deletions test/rules/comment-format/exceptions-words/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rules": {
"comment-format": [true, "check-space", "check-lowercase", {"ignoreWords": ["TODO", "HACK"]}]
},
"jsRules": {
"comment-format": [true, "check-uppercase", {"ignoreWords": ["todo"]}]
}
}
8 changes: 0 additions & 8 deletions test/rules/comment-format/exceptions/tslint.json

This file was deleted.

2 changes: 1 addition & 1 deletion test/rules/comment-format/lower/test.js.lint
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ class Clazz { // this comment is correct
//noinspection JSUnusedGlobalSymbols
const unusedVar = 'unneeded value';

[lower]: comment must start with lowercase letter or word from exceptions list
[lower]: comment must start with lowercase letter, word from exceptions list or exceptions pattern
[space]: comment must start with a space
2 changes: 1 addition & 1 deletion test/rules/comment-format/lower/test.ts.lint
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ class Clazz { // this comment is correct
//noinspection JSUnusedGlobalSymbols
const unusedVar = 'unneeded value';

[lower]: comment must start with lowercase letter or word from exceptions list
[lower]: comment must start with lowercase letter, word from exceptions list or exceptions pattern
[space]: comment must start with a space
2 changes: 1 addition & 1 deletion test/rules/comment-format/upper/test.ts.lint
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ class Invalid {}
// tslint:disable-next-line:no-unused-expression
class Valid {}

[upper]: comment must start with uppercase letter or word from exceptions list
[upper]: comment must start with uppercase letter, word from exceptions list or exceptions pattern
[space]: comment must start with a space

0 comments on commit 3d6ccef

Please sign in to comment.