Skip to content
This repository has been archived by the owner on Aug 4, 2020. It is now read-only.

Commit

Permalink
Add babel semi (#121)
Browse files Browse the repository at this point in the history
* Add semi from eslint

* Add ClassProperty support to semi rule

Fixes #43
  • Loading branch information
aaronjensen authored and Kent C. Dodds committed Feb 27, 2017
1 parent edddea6 commit 14a367f
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ original ones as well!).
"babel/object-curly-spacing": 1,
"babel/no-await-in-loop": 1,
"babel/flow-object-type": 1,
"babel/no-invalid-this": 1
"babel/no-invalid-this": 1,
"babel/semi": 1
}
}
```
Expand All @@ -45,6 +46,7 @@ Each rule corresponds to a core `eslint` rule, and has the same options.
- `babel/new-cap`: Ignores capitalized decorators (`@Decorator`)
- `babel/object-curly-spacing`: doesn't complain about `export x from "mod";` or `export * as x from "mod";` (🛠 )
- `babel/no-invalid-this`: doesn't fail when inside class properties (`class A { a = this.b; }`)
- `babel/semi`: Includes class properties (🛠 )

The following rules are not in `eslint`, but are relevant only to syntax that is not specified by
the current JavaScript standard or supported by `eslint`.
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
'flow-object-type': require('./rules/flow-object-type'),
'func-params-comma-dangle': require('./rules/func-params-comma-dangle'),
'no-invalid-this': require('./rules/no-invalid-this'),
'semi': require('./rules/semi'),
},
rulesConfig: {
'generator-star-spacing': 0,
Expand All @@ -24,5 +25,6 @@ module.exports = {
'flow-object-type': 0,
'func-params-comma-dangle': 0,
'no-invalid-this': 0,
'semi': 0,
}
};
225 changes: 225 additions & 0 deletions rules/semi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/**
* @fileoverview Rule to flag missing semicolons.
* @author Nicholas C. Zakas
*/
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "require or disallow semicolons instead of ASI",
category: "Stylistic Issues",
recommended: false
},

fixable: "code",

schema: {
anyOf: [
{
type: "array",
items: [
{
enum: ["never"]
}
],
minItems: 0,
maxItems: 1
},
{
type: "array",
items: [
{
enum: ["always"]
},
{
type: "object",
properties: {
omitLastInOneLineBlock: { type: "boolean" }
},
additionalProperties: false
}
],
minItems: 0,
maxItems: 2
}
]
}
},

create(context) {

const OPT_OUT_PATTERN = /^[-[(/+`]/; // One of [(/+-`
const options = context.options[1];
const never = context.options[0] === "never",
exceptOneLine = options && options.omitLastInOneLineBlock === true,
sourceCode = context.getSourceCode();

//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------

/**
* Reports a semicolon error with appropriate location and message.
* @param {ASTNode} node The node with an extra or missing semicolon.
* @param {boolean} missing True if the semicolon is missing.
* @returns {void}
*/
function report(node, missing) {
const lastToken = sourceCode.getLastToken(node);
let message,
fix,
loc = lastToken.loc;

if (!missing) {
message = "Missing semicolon.";
loc = loc.end;
fix = function(fixer) {
return fixer.insertTextAfter(lastToken, ";");
};
} else {
message = "Extra semicolon.";
loc = loc.start;
fix = function(fixer) {
return fixer.remove(lastToken);
};
}

context.report({
node,
loc,
message,
fix
});

}

/**
* Checks whether a token is a semicolon punctuator.
* @param {Token} token The token.
* @returns {boolean} True if token is a semicolon punctuator.
*/
function isSemicolon(token) {
return (token.type === "Punctuator" && token.value === ";");
}

/**
* Check if a semicolon is unnecessary, only true if:
* - next token is on a new line and is not one of the opt-out tokens
* - next token is a valid statement divider
* @param {Token} lastToken last token of current node.
* @returns {boolean} whether the semicolon is unnecessary.
*/
function isUnnecessarySemicolon(lastToken) {
if (!isSemicolon(lastToken)) {
return false;
}

const nextToken = sourceCode.getTokenAfter(lastToken);

if (!nextToken) {
return true;
}

const lastTokenLine = lastToken.loc.end.line;
const nextTokenLine = nextToken.loc.start.line;
const isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value) && nextToken.value !== "++" && nextToken.value !== "--";
const isDivider = (nextToken.value === "}" || nextToken.value === ";");

return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider;
}

/**
* Checks a node to see if it's in a one-liner block statement.
* @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is in a one-liner block statement.
*/
function isOneLinerBlock(node) {
const nextToken = sourceCode.getTokenAfter(node);

if (!nextToken || nextToken.value !== "}") {
return false;
}

const parent = node.parent;

return parent && parent.type === "BlockStatement" &&
parent.loc.start.line === parent.loc.end.line;
}

/**
* Checks a node to see if it's followed by a semicolon.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkForSemicolon(node) {
const lastToken = sourceCode.getLastToken(node);

if (never) {
if (isUnnecessarySemicolon(lastToken)) {
report(node, true);
}
} else {
if (!isSemicolon(lastToken)) {
if (!exceptOneLine || !isOneLinerBlock(node)) {
report(node);
}
} else {
if (exceptOneLine && isOneLinerBlock(node)) {
report(node, true);
}
}
}
}

/**
* Checks to see if there's a semicolon after a variable declaration.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkForSemicolonForVariableDeclaration(node) {
const ancestors = context.getAncestors(),
parentIndex = ancestors.length - 1,
parent = ancestors[parentIndex];

if ((parent.type !== "ForStatement" || parent.init !== node) &&
(!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node)
) {
checkForSemicolon(node);
}
}

//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------

return {
VariableDeclaration: checkForSemicolonForVariableDeclaration,
ExpressionStatement: checkForSemicolon,
ReturnStatement: checkForSemicolon,
ThrowStatement: checkForSemicolon,
DoWhileStatement: checkForSemicolon,
DebuggerStatement: checkForSemicolon,
BreakStatement: checkForSemicolon,
ContinueStatement: checkForSemicolon,
ImportDeclaration: checkForSemicolon,
ExportAllDeclaration: checkForSemicolon,
ClassProperty: checkForSemicolon,
ExportNamedDeclaration(node) {
if (!node.declaration) {
checkForSemicolon(node);
}
},
ExportDefaultDeclaration(node) {
if (!/(?:Class|Function)Declaration/.test(node.declaration.type)) {
checkForSemicolon(node);
}
}
};

}
};
Loading

0 comments on commit 14a367f

Please sign in to comment.