Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Commit

Permalink
Merge pull request #974 from Pajn/extend-semicolon-rule
Browse files Browse the repository at this point in the history
Extend semicolon rule with a never option
  • Loading branch information
adidahiya committed Feb 18, 2016
2 parents 6db495a + f2e52e3 commit 73705a5
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 10 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ A sample configuration file with all options is available [here](https://github.
* `"jsx-double"` enforces double quotes for JSX attributes.
* `"avoid-escape"` allows you to use the "other" quotemark in cases where escaping would normally be required. For example, `[true, "double", "avoid-escape"]` would not report a failure on the string literal `'Hello "World"'`.
* `radix` enforces the radix parameter of `parseInt`.
* `semicolon` enforces semicolons at the end of every statement.
* `semicolon` enforces consistent semicolon usage at the end of every statement. Rule options:
* `"always"` enforces semicolons at the end of every statement.
* `"never"` disallows semicolons at the end of every statement except for when they are necessary.
* `switch-default` enforces a `default` case in `switch` statements.
* `trailing-comma` enforces or disallows trailing comma within array and object literals, destructuring assignment and named imports.
Each rule option requires a value of `"always"` or `"never"`. Rule options:
Expand Down
2 changes: 1 addition & 1 deletion docs/sample.tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"avoid-escape"
],
"radix": true,
"semicolon": true,
"semicolon": [true, "always"],
"switch-default": true,
"trailing-comma": [
true,
Expand Down
2 changes: 1 addition & 1 deletion src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const DEFAULT_CONFIG = {
"no-var-keyword": true,
"one-line": [true, "check-open-brace", "check-whitespace"],
"quotemark": [true, "double"],
"semicolon": true,
"semicolon": [true, "always"],
"triple-equals": [true, "allow-null-check"],
"typedef-whitespace": [true, {
"call-signature": "nospace",
Expand Down
33 changes: 26 additions & 7 deletions src/rules/semicolonRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
import * as ts from "typescript";
import * as Lint from "../lint";

const OPTION_ALWAYS = "always";
const OPTION_NEVER = "never";

export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "missing semicolon";
public static FAILURE_STRING_MISSING = "missing semicolon";
public static FAILURE_STRING_UNNECESSARY = "unnecessary semicolon";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new SemicolonWalker(sourceFile, this.getOptions()));
Expand Down Expand Up @@ -90,13 +94,28 @@ class SemicolonWalker extends Lint.RuleWalker {
}

private checkSemicolonAt(node: ts.Node) {
const children = node.getChildren(this.getSourceFile());
const sourceFile = this.getSourceFile();
const children = node.getChildren(sourceFile);
const hasSemicolon = children.some((child) => child.kind === ts.SyntaxKind.SemicolonToken);

if (!hasSemicolon) {
const sourceFile = this.getSourceFile();
const position = node.getStart(sourceFile) + node.getWidth(sourceFile);
this.addFailure(this.createFailure(Math.min(position, this.getLimit()), 0, Rule.FAILURE_STRING));
const position = node.getStart(sourceFile) + node.getWidth(sourceFile);
// Backwards compatible with plain {"semicolon": true}
const always = this.hasOption(OPTION_ALWAYS) || (this.getOptions() && this.getOptions().length === 0);

if (always && !hasSemicolon) {
this.addFailure(this.createFailure(Math.min(position, this.getLimit()), 0, Rule.FAILURE_STRING_MISSING));
} else if (this.hasOption(OPTION_NEVER) && hasSemicolon) {
const scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, sourceFile.text);
scanner.setTextPos(position);

let tokenKind = scanner.scan();
while (tokenKind === ts.SyntaxKind.WhitespaceTrivia || tokenKind === ts.SyntaxKind.NewLineTrivia) {
tokenKind = scanner.scan();
}

if (tokenKind !== ts.SyntaxKind.OpenParenToken && tokenKind !== ts.SyntaxKind.OpenBracketToken
&& tokenKind !== ts.SyntaxKind.PlusToken && tokenKind !== ts.SyntaxKind.MinusToken) {
this.addFailure(this.createFailure(Math.min(position - 1, this.getLimit()), 1, Rule.FAILURE_STRING_UNNECESSARY));
}
}
}
}
5 changes: 5 additions & 0 deletions test/rules/semicolon/always/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"semicolon": [true, "always"]
}
}
74 changes: 74 additions & 0 deletions test/rules/semicolon/enabled/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
var x = 3
~nil [missing semicolon]
a += b
~nil [missing semicolon]

c = () => {
}
~nil [missing semicolon]

d = function() { }
~nil [missing semicolon]

console.log("i am adam, am i?")
~nil [missing semicolon]

function xyz() {
return
~nil [missing semicolon]
}

switch(xyz) {
case 1:
break
~nil [missing semicolon]
case 2:
continue
~nil [missing semicolon]
}

throw new Error("some error")
~nil [missing semicolon]

do {
var a = 4
~nil [missing semicolon]
} while(x == 3)
~nil [missing semicolon]

debugger
~nil [missing semicolon]

import v = require("i")
~nil [missing semicolon]
module M {
export var x
~nil [missing semicolon]
}

function useStrictMissingSemicolon() {
"use strict"
~nil [missing semicolon]
return null;
}

class MyClass {
public name : string
~nil [missing semicolon]
private index : number
~nil [missing semicolon]
private email : string;
}

interface ITest {
foo?: string
~nil [missing semicolon]
bar: number
~nil [missing semicolon]
baz: boolean;
}

import {Router} from 'aurelia-router';

import {Controller} from 'my-lib'
~nil [missing semicolon]
File renamed without changes.
106 changes: 106 additions & 0 deletions test/rules/semicolon/never/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
var x = 3;
~ [unnecessary semicolon]
a += b;
~ [unnecessary semicolon]

c = () => {
};
~ [unnecessary semicolon]

d = function() { };
~ [unnecessary semicolon]

console.log("i am adam, am i?");
~ [unnecessary semicolon]

function xyz() {
return;
~ [unnecessary semicolon]
}

switch(xyz) {
case 1:
break;
~ [unnecessary semicolon]
case 2:
continue;
~ [unnecessary semicolon]
}

throw new Error("some error");
~ [unnecessary semicolon]

do {
var a = 4;
~ [unnecessary semicolon]
} while(x == 3);
~ [unnecessary semicolon]

debugger;
~ [unnecessary semicolon]

import v = require("i");
~ [unnecessary semicolon]
module M {
export var x;
~ [unnecessary semicolon]
}

function useStrictUnnecessarySemicolon() {
"use strict";
~ [unnecessary semicolon]
return null
}

class MyClass {
public name : string;
~ [unnecessary semicolon]
private index : number;
~ [unnecessary semicolon]
private email : string
}

interface ITest {
foo?: string;
~ [unnecessary semicolon]
bar: number;
~ [unnecessary semicolon]
baz: boolean
}

import {Router} from 'aurelia-router'

import {Controller} from 'my-lib';
~ [unnecessary semicolon]

// Edge cases when not omitting semicolon needs to be supported

var a = 1;
("1" + "2").length

var a = 1;
[].length

var a = 1;
+"a"

var a = 1;
-1

var a = 1
;("1" + "2").length

var a = 1
;[].length

var a = 1
;+"a"

var a = 1
;-1

// For loops uses semicolons as well so make sure we aren't breaking those

for (var i = 0; i < 10; ++i) {
// do something
}
5 changes: 5 additions & 0 deletions test/rules/semicolon/never/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"semicolon": [true, "never"]
}
}

0 comments on commit 73705a5

Please sign in to comment.