object-curly-newline.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /**
  2. * @fileoverview Rule to require or disallow line breaks inside braces.
  3. * @author Toru Nagashima
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. //------------------------------------------------------------------------------
  12. // Helpers
  13. //------------------------------------------------------------------------------
  14. // Schema objects.
  15. const OPTION_VALUE = {
  16. oneOf: [
  17. {
  18. enum: ["always", "never"],
  19. },
  20. {
  21. type: "object",
  22. properties: {
  23. multiline: {
  24. type: "boolean",
  25. },
  26. minProperties: {
  27. type: "integer",
  28. minimum: 0,
  29. },
  30. consistent: {
  31. type: "boolean",
  32. },
  33. },
  34. additionalProperties: false,
  35. minProperties: 1,
  36. },
  37. ],
  38. };
  39. /**
  40. * Normalizes a given option value.
  41. * @param {string|Object|undefined} value An option value to parse.
  42. * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
  43. */
  44. function normalizeOptionValue(value) {
  45. let multiline = false;
  46. let minProperties = Number.POSITIVE_INFINITY;
  47. let consistent = false;
  48. if (value) {
  49. if (value === "always") {
  50. minProperties = 0;
  51. } else if (value === "never") {
  52. minProperties = Number.POSITIVE_INFINITY;
  53. } else {
  54. multiline = Boolean(value.multiline);
  55. minProperties = value.minProperties || Number.POSITIVE_INFINITY;
  56. consistent = Boolean(value.consistent);
  57. }
  58. } else {
  59. consistent = true;
  60. }
  61. return { multiline, minProperties, consistent };
  62. }
  63. /**
  64. * Checks if a value is an object.
  65. * @param {any} value The value to check
  66. * @returns {boolean} `true` if the value is an object, otherwise `false`
  67. */
  68. function isObject(value) {
  69. return typeof value === "object" && value !== null;
  70. }
  71. /**
  72. * Checks if an option is a node-specific option
  73. * @param {any} option The option to check
  74. * @returns {boolean} `true` if the option is node-specific, otherwise `false`
  75. */
  76. function isNodeSpecificOption(option) {
  77. return isObject(option) || typeof option === "string";
  78. }
  79. /**
  80. * Normalizes a given option value.
  81. * @param {string|Object|undefined} options An option value to parse.
  82. * @returns {{
  83. * ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
  84. * ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
  85. * ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
  86. * ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
  87. * }} Normalized option object.
  88. */
  89. function normalizeOptions(options) {
  90. if (
  91. isObject(options) &&
  92. Object.values(options).some(isNodeSpecificOption)
  93. ) {
  94. return {
  95. ObjectExpression: normalizeOptionValue(options.ObjectExpression),
  96. ObjectPattern: normalizeOptionValue(options.ObjectPattern),
  97. ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
  98. ExportNamedDeclaration: normalizeOptionValue(
  99. options.ExportDeclaration,
  100. ),
  101. };
  102. }
  103. const value = normalizeOptionValue(options);
  104. return {
  105. ObjectExpression: value,
  106. ObjectPattern: value,
  107. ImportDeclaration: value,
  108. ExportNamedDeclaration: value,
  109. };
  110. }
  111. /**
  112. * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
  113. * node needs to be checked for missing line breaks
  114. * @param {ASTNode} node Node under inspection
  115. * @param {Object} options option specific to node type
  116. * @param {Token} first First object property
  117. * @param {Token} last Last object property
  118. * @returns {boolean} `true` if node needs to be checked for missing line breaks
  119. */
  120. function areLineBreaksRequired(node, options, first, last) {
  121. let objectProperties;
  122. if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
  123. objectProperties = node.properties;
  124. } else {
  125. // is ImportDeclaration or ExportNamedDeclaration
  126. objectProperties = node.specifiers.filter(
  127. s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier",
  128. );
  129. }
  130. return (
  131. objectProperties.length >= options.minProperties ||
  132. (options.multiline &&
  133. objectProperties.length > 0 &&
  134. first.loc.start.line !== last.loc.end.line)
  135. );
  136. }
  137. //------------------------------------------------------------------------------
  138. // Rule Definition
  139. //------------------------------------------------------------------------------
  140. /** @type {import('../types').Rule.RuleModule} */
  141. module.exports = {
  142. meta: {
  143. deprecated: {
  144. message: "Formatting rules are being moved out of ESLint core.",
  145. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  146. deprecatedSince: "8.53.0",
  147. availableUntil: "11.0.0",
  148. replacedBy: [
  149. {
  150. message:
  151. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  152. url: "https://eslint.style/guide/migration",
  153. plugin: {
  154. name: "@stylistic/eslint-plugin",
  155. url: "https://eslint.style",
  156. },
  157. rule: {
  158. name: "object-curly-newline",
  159. url: "https://eslint.style/rules/object-curly-newline",
  160. },
  161. },
  162. ],
  163. },
  164. type: "layout",
  165. docs: {
  166. description:
  167. "Enforce consistent line breaks after opening and before closing braces",
  168. recommended: false,
  169. url: "https://eslint.org/docs/latest/rules/object-curly-newline",
  170. },
  171. fixable: "whitespace",
  172. schema: [
  173. {
  174. oneOf: [
  175. OPTION_VALUE,
  176. {
  177. type: "object",
  178. properties: {
  179. ObjectExpression: OPTION_VALUE,
  180. ObjectPattern: OPTION_VALUE,
  181. ImportDeclaration: OPTION_VALUE,
  182. ExportDeclaration: OPTION_VALUE,
  183. },
  184. additionalProperties: false,
  185. minProperties: 1,
  186. },
  187. ],
  188. },
  189. ],
  190. messages: {
  191. unexpectedLinebreakBeforeClosingBrace:
  192. "Unexpected line break before this closing brace.",
  193. unexpectedLinebreakAfterOpeningBrace:
  194. "Unexpected line break after this opening brace.",
  195. expectedLinebreakBeforeClosingBrace:
  196. "Expected a line break before this closing brace.",
  197. expectedLinebreakAfterOpeningBrace:
  198. "Expected a line break after this opening brace.",
  199. },
  200. },
  201. create(context) {
  202. const sourceCode = context.sourceCode;
  203. const normalizedOptions = normalizeOptions(context.options[0]);
  204. /**
  205. * Reports a given node if it violated this rule.
  206. * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
  207. * @returns {void}
  208. */
  209. function check(node) {
  210. const options = normalizedOptions[node.type];
  211. if (
  212. (node.type === "ImportDeclaration" &&
  213. !node.specifiers.some(
  214. specifier => specifier.type === "ImportSpecifier",
  215. )) ||
  216. (node.type === "ExportNamedDeclaration" &&
  217. !node.specifiers.some(
  218. specifier => specifier.type === "ExportSpecifier",
  219. ))
  220. ) {
  221. return;
  222. }
  223. const openBrace = sourceCode.getFirstToken(
  224. node,
  225. token => token.value === "{",
  226. );
  227. let closeBrace;
  228. if (node.typeAnnotation) {
  229. closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
  230. } else {
  231. closeBrace = sourceCode.getLastToken(
  232. node,
  233. token => token.value === "}",
  234. );
  235. }
  236. let first = sourceCode.getTokenAfter(openBrace, {
  237. includeComments: true,
  238. });
  239. let last = sourceCode.getTokenBefore(closeBrace, {
  240. includeComments: true,
  241. });
  242. const needsLineBreaks = areLineBreaksRequired(
  243. node,
  244. options,
  245. first,
  246. last,
  247. );
  248. const hasCommentsFirstToken = astUtils.isCommentToken(first);
  249. const hasCommentsLastToken = astUtils.isCommentToken(last);
  250. /*
  251. * Use tokens or comments to check multiline or not.
  252. * But use only tokens to check whether line breaks are needed.
  253. * This allows:
  254. * var obj = { // eslint-disable-line foo
  255. * a: 1
  256. * }
  257. */
  258. first = sourceCode.getTokenAfter(openBrace);
  259. last = sourceCode.getTokenBefore(closeBrace);
  260. if (needsLineBreaks) {
  261. if (astUtils.isTokenOnSameLine(openBrace, first)) {
  262. context.report({
  263. messageId: "expectedLinebreakAfterOpeningBrace",
  264. node,
  265. loc: openBrace.loc,
  266. fix(fixer) {
  267. if (hasCommentsFirstToken) {
  268. return null;
  269. }
  270. return fixer.insertTextAfter(openBrace, "\n");
  271. },
  272. });
  273. }
  274. if (astUtils.isTokenOnSameLine(last, closeBrace)) {
  275. context.report({
  276. messageId: "expectedLinebreakBeforeClosingBrace",
  277. node,
  278. loc: closeBrace.loc,
  279. fix(fixer) {
  280. if (hasCommentsLastToken) {
  281. return null;
  282. }
  283. return fixer.insertTextBefore(closeBrace, "\n");
  284. },
  285. });
  286. }
  287. } else {
  288. const consistent = options.consistent;
  289. const hasLineBreakBetweenOpenBraceAndFirst =
  290. !astUtils.isTokenOnSameLine(openBrace, first);
  291. const hasLineBreakBetweenCloseBraceAndLast =
  292. !astUtils.isTokenOnSameLine(last, closeBrace);
  293. if (
  294. (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
  295. (consistent &&
  296. hasLineBreakBetweenOpenBraceAndFirst &&
  297. !hasLineBreakBetweenCloseBraceAndLast)
  298. ) {
  299. context.report({
  300. messageId: "unexpectedLinebreakAfterOpeningBrace",
  301. node,
  302. loc: openBrace.loc,
  303. fix(fixer) {
  304. if (hasCommentsFirstToken) {
  305. return null;
  306. }
  307. return fixer.removeRange([
  308. openBrace.range[1],
  309. first.range[0],
  310. ]);
  311. },
  312. });
  313. }
  314. if (
  315. (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
  316. (consistent &&
  317. !hasLineBreakBetweenOpenBraceAndFirst &&
  318. hasLineBreakBetweenCloseBraceAndLast)
  319. ) {
  320. context.report({
  321. messageId: "unexpectedLinebreakBeforeClosingBrace",
  322. node,
  323. loc: closeBrace.loc,
  324. fix(fixer) {
  325. if (hasCommentsLastToken) {
  326. return null;
  327. }
  328. return fixer.removeRange([
  329. last.range[1],
  330. closeBrace.range[0],
  331. ]);
  332. },
  333. });
  334. }
  335. }
  336. }
  337. return {
  338. ObjectExpression: check,
  339. ObjectPattern: check,
  340. ImportDeclaration: check,
  341. ExportNamedDeclaration: check,
  342. };
  343. },
  344. };