439 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Rule to flag missing semicolons.
 | |
|  * @author Nicholas C. Zakas
 | |
|  * @deprecated in ESLint v8.53.0
 | |
|  */
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const FixTracker = require("./utils/fix-tracker");
 | |
| const astUtils = require("./utils/ast-utils");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /** @type {import('../shared/types').Rule} */
 | |
| module.exports = {
 | |
|     meta: {
 | |
|         deprecated: true,
 | |
|         replacedBy: [],
 | |
|         type: "layout",
 | |
| 
 | |
|         docs: {
 | |
|             description: "Require or disallow semicolons instead of ASI",
 | |
|             recommended: false,
 | |
|             url: "https://eslint.org/docs/latest/rules/semi"
 | |
|         },
 | |
| 
 | |
|         fixable: "code",
 | |
| 
 | |
|         schema: {
 | |
|             anyOf: [
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["never"]
 | |
|                         },
 | |
|                         {
 | |
|                             type: "object",
 | |
|                             properties: {
 | |
|                                 beforeStatementContinuationChars: {
 | |
|                                     enum: ["always", "any", "never"]
 | |
|                                 }
 | |
|                             },
 | |
|                             additionalProperties: false
 | |
|                         }
 | |
|                     ],
 | |
|                     minItems: 0,
 | |
|                     maxItems: 2
 | |
|                 },
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["always"]
 | |
|                         },
 | |
|                         {
 | |
|                             type: "object",
 | |
|                             properties: {
 | |
|                                 omitLastInOneLineBlock: { type: "boolean" },
 | |
|                                 omitLastInOneLineClassBody: { type: "boolean" }
 | |
|                             },
 | |
|                             additionalProperties: false
 | |
|                         }
 | |
|                     ],
 | |
|                     minItems: 0,
 | |
|                     maxItems: 2
 | |
|                 }
 | |
|             ]
 | |
|         },
 | |
| 
 | |
|         messages: {
 | |
|             missingSemi: "Missing semicolon.",
 | |
|             extraSemi: "Extra semicolon."
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     create(context) {
 | |
| 
 | |
|         const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-`
 | |
|         const unsafeClassFieldNames = new Set(["get", "set", "static"]);
 | |
|         const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]);
 | |
|         const options = context.options[1];
 | |
|         const never = context.options[0] === "never";
 | |
|         const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
 | |
|         const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody);
 | |
|         const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
 | |
|         const sourceCode = context.sourceCode;
 | |
| 
 | |
|         //--------------------------------------------------------------------------
 | |
|         // 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 messageId,
 | |
|                 fix,
 | |
|                 loc;
 | |
| 
 | |
|             if (!missing) {
 | |
|                 messageId = "missingSemi";
 | |
|                 loc = {
 | |
|                     start: lastToken.loc.end,
 | |
|                     end: astUtils.getNextLocation(sourceCode, lastToken.loc.end)
 | |
|                 };
 | |
|                 fix = function(fixer) {
 | |
|                     return fixer.insertTextAfter(lastToken, ";");
 | |
|                 };
 | |
|             } else {
 | |
|                 messageId = "extraSemi";
 | |
|                 loc = lastToken.loc;
 | |
|                 fix = function(fixer) {
 | |
| 
 | |
|                     /*
 | |
|                      * Expand the replacement range to include the surrounding
 | |
|                      * tokens to avoid conflicting with no-extra-semi.
 | |
|                      * https://github.com/eslint/eslint/issues/7928
 | |
|                      */
 | |
|                     return new FixTracker(fixer, sourceCode)
 | |
|                         .retainSurroundingTokens(lastToken)
 | |
|                         .remove(lastToken);
 | |
|                 };
 | |
|             }
 | |
| 
 | |
|             context.report({
 | |
|                 node,
 | |
|                 loc,
 | |
|                 messageId,
 | |
|                 fix
 | |
|             });
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check whether a given semicolon token is redundant.
 | |
|          * @param {Token} semiToken A semicolon token to check.
 | |
|          * @returns {boolean} `true` if the next token is `;` or `}`.
 | |
|          */
 | |
|         function isRedundantSemi(semiToken) {
 | |
|             const nextToken = sourceCode.getTokenAfter(semiToken);
 | |
| 
 | |
|             return (
 | |
|                 !nextToken ||
 | |
|                 astUtils.isClosingBraceToken(nextToken) ||
 | |
|                 astUtils.isSemicolonToken(nextToken)
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check whether a given token is the closing brace of an arrow function.
 | |
|          * @param {Token} lastToken A token to check.
 | |
|          * @returns {boolean} `true` if the token is the closing brace of an arrow function.
 | |
|          */
 | |
|         function isEndOfArrowBlock(lastToken) {
 | |
|             if (!astUtils.isClosingBraceToken(lastToken)) {
 | |
|                 return false;
 | |
|             }
 | |
|             const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
 | |
| 
 | |
|             return (
 | |
|                 node.type === "BlockStatement" &&
 | |
|                 node.parent.type === "ArrowFunctionExpression"
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if a given PropertyDefinition node followed by a semicolon
 | |
|          * can safely remove that semicolon. It is not to safe to remove if
 | |
|          * the class field name is "get", "set", or "static", or if
 | |
|          * followed by a generator method.
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {boolean} `true` if the node cannot have the semicolon
 | |
|          *      removed.
 | |
|          */
 | |
|         function maybeClassFieldAsiHazard(node) {
 | |
| 
 | |
|             if (node.type !== "PropertyDefinition") {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             /*
 | |
|              * Computed property names and non-identifiers are always safe
 | |
|              * as they can be distinguished from keywords easily.
 | |
|              */
 | |
|             const needsNameCheck = !node.computed && node.key.type === "Identifier";
 | |
| 
 | |
|             /*
 | |
|              * Certain names are problematic unless they also have a
 | |
|              * a way to distinguish between keywords and property
 | |
|              * names.
 | |
|              */
 | |
|             if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) {
 | |
| 
 | |
|                 /*
 | |
|                  * Special case: If the field name is `static`,
 | |
|                  * it is only valid if the field is marked as static,
 | |
|                  * so "static static" is okay but "static" is not.
 | |
|                  */
 | |
|                 const isStaticStatic = node.static && node.key.name === "static";
 | |
| 
 | |
|                 /*
 | |
|                  * For other unsafe names, we only care if there is no
 | |
|                  * initializer. No initializer = hazard.
 | |
|                  */
 | |
|                 if (!isStaticStatic && !node.value) {
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const followingToken = sourceCode.getTokenAfter(node);
 | |
| 
 | |
|             return unsafeClassFieldFollowers.has(followingToken.value);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check whether a given node is on the same line with the next token.
 | |
|          * @param {Node} node A statement node to check.
 | |
|          * @returns {boolean} `true` if the node is on the same line with the next token.
 | |
|          */
 | |
|         function isOnSameLineWithNextToken(node) {
 | |
|             const prevToken = sourceCode.getLastToken(node, 1);
 | |
|             const nextToken = sourceCode.getTokenAfter(node);
 | |
| 
 | |
|             return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check whether a given node can connect the next line if the next line is unreliable.
 | |
|          * @param {Node} node A statement node to check.
 | |
|          * @returns {boolean} `true` if the node can connect the next line.
 | |
|          */
 | |
|         function maybeAsiHazardAfter(node) {
 | |
|             const t = node.type;
 | |
| 
 | |
|             if (t === "DoWhileStatement" ||
 | |
|                 t === "BreakStatement" ||
 | |
|                 t === "ContinueStatement" ||
 | |
|                 t === "DebuggerStatement" ||
 | |
|                 t === "ImportDeclaration" ||
 | |
|                 t === "ExportAllDeclaration"
 | |
|             ) {
 | |
|                 return false;
 | |
|             }
 | |
|             if (t === "ReturnStatement") {
 | |
|                 return Boolean(node.argument);
 | |
|             }
 | |
|             if (t === "ExportNamedDeclaration") {
 | |
|                 return Boolean(node.declaration);
 | |
|             }
 | |
|             if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check whether a given token can connect the previous statement.
 | |
|          * @param {Token} token A token to check.
 | |
|          * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`.
 | |
|          */
 | |
|         function maybeAsiHazardBefore(token) {
 | |
|             return (
 | |
|                 Boolean(token) &&
 | |
|                 OPT_OUT_PATTERN.test(token.value) &&
 | |
|                 token.value !== "++" &&
 | |
|                 token.value !== "--"
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Check if the semicolon of a given node is unnecessary, only true if:
 | |
|          *   - next token is a valid statement divider (`;` or `}`).
 | |
|          *   - next token is on a new line and the node is not connectable to the new line.
 | |
|          * @param {Node} node A statement node to check.
 | |
|          * @returns {boolean} whether the semicolon is unnecessary.
 | |
|          */
 | |
|         function canRemoveSemicolon(node) {
 | |
|             if (isRedundantSemi(sourceCode.getLastToken(node))) {
 | |
|                 return true; // `;;` or `;}`
 | |
|             }
 | |
|             if (maybeClassFieldAsiHazard(node)) {
 | |
|                 return false;
 | |
|             }
 | |
|             if (isOnSameLineWithNextToken(node)) {
 | |
|                 return false; // One liner.
 | |
|             }
 | |
| 
 | |
|             // continuation characters should not apply to class fields
 | |
|             if (
 | |
|                 node.type !== "PropertyDefinition" &&
 | |
|                 beforeStatementContinuationChars === "never" &&
 | |
|                 !maybeAsiHazardAfter(node)
 | |
|             ) {
 | |
|                 return true; // ASI works. This statement doesn't connect to the next.
 | |
|             }
 | |
|             if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
 | |
|                 return true; // ASI works. The next token doesn't connect to this statement.
 | |
|             }
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks a node to see if it's the last item in a one-liner block.
 | |
|          * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its
 | |
|          * braces (and consequently everything between them) are on the same line.
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {boolean} whether the node is the last item in a one-liner block.
 | |
|          */
 | |
|         function isLastInOneLinerBlock(node) {
 | |
|             const parent = node.parent;
 | |
|             const nextToken = sourceCode.getTokenAfter(node);
 | |
| 
 | |
|             if (!nextToken || nextToken.value !== "}") {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             if (parent.type === "BlockStatement") {
 | |
|                 return parent.loc.start.line === parent.loc.end.line;
 | |
|             }
 | |
| 
 | |
|             if (parent.type === "StaticBlock") {
 | |
|                 const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 }); // skip the `static` token
 | |
| 
 | |
|                 return openingBrace.loc.start.line === parent.loc.end.line;
 | |
|             }
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks a node to see if it's the last item in a one-liner `ClassBody` node.
 | |
|          * ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line.
 | |
|          * @param {ASTNode} node The node to check.
 | |
|          * @returns {boolean} whether the node is the last item in a one-liner ClassBody.
 | |
|          */
 | |
|         function isLastInOneLinerClassBody(node) {
 | |
|             const parent = node.parent;
 | |
|             const nextToken = sourceCode.getTokenAfter(node);
 | |
| 
 | |
|             if (!nextToken || nextToken.value !== "}") {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             if (parent.type === "ClassBody") {
 | |
|                 return parent.loc.start.line === parent.loc.end.line;
 | |
|             }
 | |
| 
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * 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 isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node));
 | |
| 
 | |
|             if (never) {
 | |
|                 if (isSemi && canRemoveSemicolon(node)) {
 | |
|                     report(node, true);
 | |
|                 } else if (
 | |
|                     !isSemi && beforeStatementContinuationChars === "always" &&
 | |
|                     node.type !== "PropertyDefinition" &&
 | |
|                     maybeAsiHazardBefore(sourceCode.getTokenAfter(node))
 | |
|                 ) {
 | |
|                     report(node);
 | |
|                 }
 | |
|             } else {
 | |
|                 const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node));
 | |
|                 const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node));
 | |
|                 const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody;
 | |
| 
 | |
|                 if (isSemi && oneLinerBlockOrClassBody) {
 | |
|                     report(node, true);
 | |
|                 } else if (!isSemi && !oneLinerBlockOrClassBody) {
 | |
|                     report(node);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * 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 parent = node.parent;
 | |
| 
 | |
|             if ((parent.type !== "ForStatement" || parent.init !== node) &&
 | |
|                 (!/^For(?:In|Of)Statement/u.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,
 | |
|             ExportNamedDeclaration(node) {
 | |
|                 if (!node.declaration) {
 | |
|                     checkForSemicolon(node);
 | |
|                 }
 | |
|             },
 | |
|             ExportDefaultDeclaration(node) {
 | |
|                 if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) {
 | |
|                     checkForSemicolon(node);
 | |
|                 }
 | |
|             },
 | |
|             PropertyDefinition: checkForSemicolon
 | |
|         };
 | |
| 
 | |
|     }
 | |
| };
 |