This repository has been archived by the owner on Aug 4, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add semi from eslint * Add ClassProperty support to semi rule Fixes #43
- Loading branch information
1 parent
edddea6
commit 14a367f
Showing
4 changed files
with
421 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
}; | ||
|
||
} | ||
}; |
Oops, something went wrong.