From 6aecaf6e36cb0b238d9c242bc3f63ed8d6252597 Mon Sep 17 00:00:00 2001 From: Abdul Al-Hasany Date: Sat, 27 Jul 2024 23:27:25 +1000 Subject: [PATCH] feat: add array.items-linebreak rule --- package.json | 2 +- schema.json | 17 ++ taqwim/package.json | 2 +- taqwim/src/rules/array/items-linebreak.ts | 183 ++++++++++++++++++ taqwim/src/rules/index.ts | 1 + taqwim/src/utils/find-ahead.ts | 2 +- .../rules/array.items-linebreak/correct-1.php | 42 ++++ .../rules/array.items-linebreak/correct-2.php | 5 + .../rules/array.items-linebreak/data.json | 16 ++ .../array.items-linebreak/fixer/1-from.php | 37 ++++ .../array.items-linebreak/fixer/1-to.php | 22 +++ .../array.items-linebreak/incorrect-1.php | 27 +++ 12 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 taqwim/src/rules/array/items-linebreak.ts create mode 100644 taqwim/test/rules/array.items-linebreak/correct-1.php create mode 100644 taqwim/test/rules/array.items-linebreak/correct-2.php create mode 100644 taqwim/test/rules/array.items-linebreak/data.json create mode 100644 taqwim/test/rules/array.items-linebreak/fixer/1-from.php create mode 100644 taqwim/test/rules/array.items-linebreak/fixer/1-to.php create mode 100644 taqwim/test/rules/array.items-linebreak/incorrect-1.php diff --git a/package.json b/package.json index 26bad9f..cadf44a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "taqwim", "displayName": "PHPTaqwim", "description": "PHP linter and formatter", - "version": "0.0.63", + "version": "0.0.64", "homepage": "https://taqwim.kalimah-apps.com/", "repository": { "type": "git", diff --git a/schema.json b/schema.json index 617df01..e8b11c2 100644 --- a/schema.json +++ b/schema.json @@ -904,6 +904,23 @@ } ] }, + "taqwim/array.items-linebreak": { + "description": "Break array into multiple lines or group them into a single line based on the position of the first item", + "oneOf": [ + { + "$ref": "#/definitions/severity" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "severity": { + "$ref": "#/definitions/severity" + } + } + } + ] + }, "taqwim/array.syntax": { "description": "Ensure that array syntax is consistent", "oneOf": [ diff --git a/taqwim/package.json b/taqwim/package.json index 9834f73..34b3890 100644 --- a/taqwim/package.json +++ b/taqwim/package.json @@ -2,7 +2,7 @@ "name": "@kalimahapps/taqwim", "description": "PHP linter and formatter", "author": "khr2003", - "version": "0.0.63", + "version": "0.0.64", "homepage": "https://taqwim.kalimah-apps.com/docs/", "repository": { "type": "git", diff --git a/taqwim/src/rules/array/items-linebreak.ts b/taqwim/src/rules/array/items-linebreak.ts new file mode 100644 index 0000000..c83bc4a --- /dev/null +++ b/taqwim/src/rules/array/items-linebreak.ts @@ -0,0 +1,183 @@ +/** + * Break array items into multiple lines or group them into a single line + * based on the position of the first item + */ +import type { AstArray, RuleContext, RuleDataOptional } from '@taqwim/types'; +class ArrayItemsLineBreak { + /** + * Rule context + */ + context = {} as RuleContext; + + /** + * Break array items into multiple lines + * + * @param {AstArray['items']} items Array items to process + */ + reportAndFixBreaking(items: AstArray['items']) { + const { report, node } = this.context; + const { loc: arrayLoc } = node as AstArray; + + for (const [index, item] of items.entries()) { + const { loc } = item; + const nextItem = items[index + 1]; + let endLoc = nextItem?.loc.start; + if (endLoc === undefined) { + // -1 to remove location of ) or ] from the end of the array + endLoc = { + line: arrayLoc.end.line, + column: arrayLoc.end.column - 1, + offset: arrayLoc.end.offset - 1, + }; + } + + // If current item and the next item are not on the same line + // then there is no need to add a line break + if (loc.end.line !== endLoc.line) { + continue; + } + + const fixPoisition = { + start: loc.end, + end: endLoc, + }; + + report({ + message: 'There should be a line break after this item.', + position: { + start: { + line: loc.end.line, + column: loc.end.column - 2, + offset: loc.end.offset - 2, + }, + end: { + line: loc.end.line, + column: loc.end.column, + offset: loc.end.offset, + }, + }, + fix(fixer) { + return fixer.after(fixPoisition, '\n'); + }, + }); + } + } + + /** + * Group array items into a single line + * + * @param {AstArray['items']} items Array items to process + */ + reportAndFixGrouping(items: AstArray['items']) { + const { report, sourceLines, node, sourceCode } = this.context; + const { loc: arrayLoc } = node as AstArray; + for (const [index, item] of items.entries()) { + const { loc } = item; + const nextItem = items[index + 1]; + let endLoc = nextItem?.loc.start; + if (endLoc === undefined) { + // -1 to remove location of ) or ] from the end of the array + endLoc = { + line: arrayLoc.end.line, + column: arrayLoc.end.column - 1, + offset: arrayLoc.end.offset - 1, + }; + } + + if (loc.end.line === endLoc.line) { + continue; + } + + const lastLine = sourceLines[loc.end.line]; + const postItemContent = lastLine.slice(loc.end.column); + + // Check for comma after the item, because last item might not + // have a dangling comma + const hasComma = postItemContent.match(/\s*,/u); + const commaIndex = hasComma?.index === undefined ? 0 : hasComma.index + 1; + + const fixerPosition = { + start: { + line: loc.end.line, + column: loc.end.column + commaIndex, + offset: loc.end.offset + commaIndex, + }, + end: endLoc, + }; + + // Make sure that the content removed in fixer is only whitespace + const contentToReplace = sourceCode.slice( + fixerPosition.start.offset, + fixerPosition.end.offset + ); + const isOnlyWhitespace = contentToReplace.trim() === ''; + if (!isOnlyWhitespace) { + continue; + } + report({ + message: 'There should not be a line break after this item.', + + // Show the message at the end of the item + position: { + start: { + line: loc.end.line, + column: loc.end.column - 2, + offset: loc.end.offset - 2, + }, + end: { + line: loc.end.line, + column: loc.end.column + commaIndex, + offset: loc.end.offset + commaIndex, + }, + }, + fix(fixer) { + return fixer.replaceRange(fixerPosition, ' '); + }, + }); + } + } + + /** + * Process the rule + * + * @param {RuleContext} context Rule context + */ + process (context: RuleContext) { + this.context = context; + const { node } = context; + const { items, loc: arrayLoc } = node as AstArray; + + if (items.length === 0) { + return; + } + + const firstItem = items[0]; + const isSameLine = arrayLoc.start.line === firstItem.loc.start.line; + + if (isSameLine !== true) { + this.reportAndFixBreaking(items); + return; + } + + // If array start and end lines are on the same line + // then there is no need to group them into a single line + // as it will be a single line array + if (arrayLoc.start.line === arrayLoc.end.line) { + return; + } + this.reportAndFixGrouping(items); + } +} + +export default (): RuleDataOptional => { + return { + meta: { + description: 'Break array items into multiple lines or group them into a single line based on the position of the first item', + fixable: true, + preset: 'taqwim', + }, + name: 'array.items-linebreak', + register: ['array'], + bindClass: ArrayItemsLineBreak, + }; +}; diff --git a/taqwim/src/rules/index.ts b/taqwim/src/rules/index.ts index 5bdf654..2f4828c 100644 --- a/taqwim/src/rules/index.ts +++ b/taqwim/src/rules/index.ts @@ -26,6 +26,7 @@ export { default as propertyLimit } from '@taqwim/rules/property-limit.js'; export { default as propertyNoVar } from '@taqwim/rules/property-no-var.js'; export { default as typeCheck } from '@taqwim/rules/type-check.js'; export { default as arrayCommaDangle } from '@taqwim/rules/array/comma-dangle.js'; +export { default as arrayItemsLinebreak } from '@taqwim/rules/array/items-linebreak.js'; export { default as arraySyntax } from '@taqwim/rules/array/syntax.js'; export { default as methodBan } from '@taqwim/rules/method/ban.js'; export { default as methodBreakParameters } from '@taqwim/rules/method/break-parameters.js'; diff --git a/taqwim/src/utils/find-ahead.ts b/taqwim/src/utils/find-ahead.ts index e69f1e1..854c4e8 100644 --- a/taqwim/src/utils/find-ahead.ts +++ b/taqwim/src/utils/find-ahead.ts @@ -83,7 +83,7 @@ const findAhead = ( * * @param {RegExpMatchArray} match The match to get the groups from * @param {Loc} position The position of the entire match - * @param {number} columnAdjust How much to adjust the column with based on + * @param {number} columnAdjust How much to adjust the column with based on * the start of the match. * @return {MatchGroupType} The groups with location */ diff --git a/taqwim/test/rules/array.items-linebreak/correct-1.php b/taqwim/test/rules/array.items-linebreak/correct-1.php new file mode 100644 index 0000000..a1b7f64 --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/correct-1.php @@ -0,0 +1,42 @@ + "bar", + "bar" => "foo", + 100 => -100, + -100 => 100, +); + +$bigone = [ +'name' => 'bigone', +'children' => [ + '1a' => 'child', + '11b' => 'child', + '111c' => 'child', + 'children' => [ + 'child' => 'aaa', + ], + ], +'short_name' => 'big' +]; \ No newline at end of file diff --git a/taqwim/test/rules/array.items-linebreak/correct-2.php b/taqwim/test/rules/array.items-linebreak/correct-2.php new file mode 100644 index 0000000..1e7add0 --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/correct-2.php @@ -0,0 +1,5 @@ +id . '"', (int) $has_sessions,); \ No newline at end of file diff --git a/taqwim/test/rules/array.items-linebreak/data.json b/taqwim/test/rules/array.items-linebreak/data.json new file mode 100644 index 0000000..c974001 --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/data.json @@ -0,0 +1,16 @@ +{ + "default": { + "correct-1": { + "description": "Array items are on separate lines", + "expected": 0 + }, + "correct-2": { + "description": "Arrays items are on the same line", + "expected": 0 + }, + "incorrect-1": { + "description": "Array items do not have consistent line breaks", + "expected": 16 + } + } +} \ No newline at end of file diff --git a/taqwim/test/rules/array.items-linebreak/fixer/1-from.php b/taqwim/test/rules/array.items-linebreak/fixer/1-from.php new file mode 100644 index 0000000..22a37e1 --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/fixer/1-from.php @@ -0,0 +1,37 @@ + "bar", + "bar" => "foo", + 100 => -100, -100 => 100, +); + +$bigone = ['name' => 'bigone', +'children' => [ + '1a' => 'child', '11b' => 'child', + '111c' => 'child', + 'children' => [ 'child' => 'aaa', + ], + ],'short_name' => 'big' +]; \ No newline at end of file diff --git a/taqwim/test/rules/array.items-linebreak/fixer/1-to.php b/taqwim/test/rules/array.items-linebreak/fixer/1-to.php new file mode 100644 index 0000000..2ef229e --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/fixer/1-to.php @@ -0,0 +1,22 @@ + "bar", "bar" => "foo", 100 => -100, -100 => 100, ); + +$bigone = ['name' => 'bigone', 'children' => [ + '1a' => 'child', +'11b' => 'child', + '111c' => 'child', + 'children' => [ 'child' => 'aaa', ], + ],'short_name' => 'big' ]; \ No newline at end of file diff --git a/taqwim/test/rules/array.items-linebreak/incorrect-1.php b/taqwim/test/rules/array.items-linebreak/incorrect-1.php new file mode 100644 index 0000000..d312fdc --- /dev/null +++ b/taqwim/test/rules/array.items-linebreak/incorrect-1.php @@ -0,0 +1,27 @@ + "bar", + "bar" => "foo", + 100 => -100, -100 => 100, +); + +$bigone = ['name' => 'bigone', +'children' => [ + '1a' => 'child', '11b' => 'child', + '111c' => 'child', + 'children' => [ 'child' => 'aaa', + ], + ],'short_name' => 'big' +]; \ No newline at end of file