Permalink
Cannot retrieve contributors at this time
| /** | |
| * @fileoverview Rule to require or disallow line breaks inside braces. | |
| * @author Toru Nagashima | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
| const astUtils = require("./utils/ast-utils"); | |
| const lodash = require("lodash"); | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
| // Schema objects. | |
| const OPTION_VALUE = { | |
| oneOf: [ | |
| { | |
| enum: ["always", "never"] | |
| }, | |
| { | |
| type: "object", | |
| properties: { | |
| multiline: { | |
| type: "boolean" | |
| }, | |
| minProperties: { | |
| type: "integer", | |
| minimum: 0 | |
| }, | |
| consistent: { | |
| type: "boolean" | |
| } | |
| }, | |
| additionalProperties: false, | |
| minProperties: 1 | |
| } | |
| ] | |
| }; | |
| /** | |
| * Normalizes a given option value. | |
| * @param {string|Object|undefined} value An option value to parse. | |
| * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object. | |
| */ | |
| function normalizeOptionValue(value) { | |
| let multiline = false; | |
| let minProperties = Number.POSITIVE_INFINITY; | |
| let consistent = false; | |
| if (value) { | |
| if (value === "always") { | |
| minProperties = 0; | |
| } else if (value === "never") { | |
| minProperties = Number.POSITIVE_INFINITY; | |
| } else { | |
| multiline = Boolean(value.multiline); | |
| minProperties = value.minProperties || Number.POSITIVE_INFINITY; | |
| consistent = Boolean(value.consistent); | |
| } | |
| } else { | |
| consistent = true; | |
| } | |
| return { multiline, minProperties, consistent }; | |
| } | |
| /** | |
| * Normalizes a given option value. | |
| * @param {string|Object|undefined} options An option value to parse. | |
| * @returns {{ | |
| * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean}, | |
| * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean}, | |
| * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean}, | |
| * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean} | |
| * }} Normalized option object. | |
| */ | |
| function normalizeOptions(options) { | |
| const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]); | |
| if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) { | |
| return { | |
| ObjectExpression: normalizeOptionValue(options.ObjectExpression), | |
| ObjectPattern: normalizeOptionValue(options.ObjectPattern), | |
| ImportDeclaration: normalizeOptionValue(options.ImportDeclaration), | |
| ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration) | |
| }; | |
| } | |
| const value = normalizeOptionValue(options); | |
| return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value }; | |
| } | |
| /** | |
| * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration | |
| * node needs to be checked for missing line breaks | |
| * @param {ASTNode} node Node under inspection | |
| * @param {Object} options option specific to node type | |
| * @param {Token} first First object property | |
| * @param {Token} last Last object property | |
| * @returns {boolean} `true` if node needs to be checked for missing line breaks | |
| */ | |
| function areLineBreaksRequired(node, options, first, last) { | |
| let objectProperties; | |
| if (node.type === "ObjectExpression" || node.type === "ObjectPattern") { | |
| objectProperties = node.properties; | |
| } else { | |
| // is ImportDeclaration or ExportNamedDeclaration | |
| objectProperties = node.specifiers | |
| .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier"); | |
| } | |
| return objectProperties.length >= options.minProperties || | |
| ( | |
| options.multiline && | |
| objectProperties.length > 0 && | |
| first.loc.start.line !== last.loc.end.line | |
| ); | |
| } | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "layout", | |
| docs: { | |
| description: "enforce consistent line breaks inside braces", | |
| category: "Stylistic Issues", | |
| recommended: false, | |
| url: "https://eslint.org/docs/rules/object-curly-newline" | |
| }, | |
| fixable: "whitespace", | |
| schema: [ | |
| { | |
| oneOf: [ | |
| OPTION_VALUE, | |
| { | |
| type: "object", | |
| properties: { | |
| ObjectExpression: OPTION_VALUE, | |
| ObjectPattern: OPTION_VALUE, | |
| ImportDeclaration: OPTION_VALUE, | |
| ExportDeclaration: OPTION_VALUE | |
| }, | |
| additionalProperties: false, | |
| minProperties: 1 | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| create(context) { | |
| const sourceCode = context.getSourceCode(); | |
| const normalizedOptions = normalizeOptions(context.options[0]); | |
| /** | |
| * Reports a given node if it violated this rule. | |
| * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node. | |
| * @returns {void} | |
| */ | |
| function check(node) { | |
| const options = normalizedOptions[node.type]; | |
| if ( | |
| (node.type === "ImportDeclaration" && | |
| !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) || | |
| (node.type === "ExportNamedDeclaration" && | |
| !node.specifiers.some(specifier => specifier.type === "ExportSpecifier")) | |
| ) { | |
| return; | |
| } | |
| const openBrace = sourceCode.getFirstToken(node, token => token.value === "{"); | |
| let closeBrace; | |
| if (node.typeAnnotation) { | |
| closeBrace = sourceCode.getTokenBefore(node.typeAnnotation); | |
| } else { | |
| closeBrace = sourceCode.getLastToken(node, token => token.value === "}"); | |
| } | |
| let first = sourceCode.getTokenAfter(openBrace, { includeComments: true }); | |
| let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); | |
| const needsLineBreaks = areLineBreaksRequired(node, options, first, last); | |
| const hasCommentsFirstToken = astUtils.isCommentToken(first); | |
| const hasCommentsLastToken = astUtils.isCommentToken(last); | |
| /* | |
| * Use tokens or comments to check multiline or not. | |
| * But use only tokens to check whether line breaks are needed. | |
| * This allows: | |
| * var obj = { // eslint-disable-line foo | |
| * a: 1 | |
| * } | |
| */ | |
| first = sourceCode.getTokenAfter(openBrace); | |
| last = sourceCode.getTokenBefore(closeBrace); | |
| if (needsLineBreaks) { | |
| if (astUtils.isTokenOnSameLine(openBrace, first)) { | |
| context.report({ | |
| message: "Expected a line break after this opening brace.", | |
| node, | |
| loc: openBrace.loc.start, | |
| fix(fixer) { | |
| if (hasCommentsFirstToken) { | |
| return null; | |
| } | |
| return fixer.insertTextAfter(openBrace, "\n"); | |
| } | |
| }); | |
| } | |
| if (astUtils.isTokenOnSameLine(last, closeBrace)) { | |
| context.report({ | |
| message: "Expected a line break before this closing brace.", | |
| node, | |
| loc: closeBrace.loc.start, | |
| fix(fixer) { | |
| if (hasCommentsLastToken) { | |
| return null; | |
| } | |
| return fixer.insertTextBefore(closeBrace, "\n"); | |
| } | |
| }); | |
| } | |
| } else { | |
| const consistent = options.consistent; | |
| const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first); | |
| const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace); | |
| if ( | |
| (!consistent && hasLineBreakBetweenOpenBraceAndFirst) || | |
| (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast) | |
| ) { | |
| context.report({ | |
| message: "Unexpected line break after this opening brace.", | |
| node, | |
| loc: openBrace.loc.start, | |
| fix(fixer) { | |
| if (hasCommentsFirstToken) { | |
| return null; | |
| } | |
| return fixer.removeRange([ | |
| openBrace.range[1], | |
| first.range[0] | |
| ]); | |
| } | |
| }); | |
| } | |
| if ( | |
| (!consistent && hasLineBreakBetweenCloseBraceAndLast) || | |
| (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast) | |
| ) { | |
| context.report({ | |
| message: "Unexpected line break before this closing brace.", | |
| node, | |
| loc: closeBrace.loc.start, | |
| fix(fixer) { | |
| if (hasCommentsLastToken) { | |
| return null; | |
| } | |
| return fixer.removeRange([ | |
| last.range[1], | |
| closeBrace.range[0] | |
| ]); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| return { | |
| ObjectExpression: check, | |
| ObjectPattern: check, | |
| ImportDeclaration: check, | |
| ExportNamedDeclaration: check | |
| }; | |
| } | |
| }; |