From 41332ca58c9675cea0edf5dcc1e0680b1d577c63 Mon Sep 17 00:00:00 2001 From: Rob Reed Date: Sat, 29 Nov 2025 16:34:44 -0800 Subject: [PATCH 1/4] Ignore multi unbalanced open/close pairs on the same line. --- src/formatters/MultiLineItemFormatter.spec.ts | 17 +++++++++ src/formatters/MultiLineItemFormatter.ts | 36 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/formatters/MultiLineItemFormatter.spec.ts b/src/formatters/MultiLineItemFormatter.spec.ts index 3968d67..e4a5cea 100644 --- a/src/formatters/MultiLineItemFormatter.spec.ts +++ b/src/formatters/MultiLineItemFormatter.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { Formatter } from '../Formatter'; +import { undent } from 'undent'; describe('MultiLineItemFormatter', () => { let formatter: Formatter; @@ -26,4 +27,20 @@ describe('MultiLineItemFormatter', () => { const expected = `sub foo()\n return { a: 1,\n b: 2\n }\nend sub`; expect(formatter.format(input).trim()).to.equal(expected.trim()); }); + + it('preserves multiple open/close pairs that are unbalanced', () => { + const input = undent` + m.callback.Invoke([m, { + "event": event, + "data": data, + }]) + `; + const expected = undent` + m.callback.Invoke([m, { + "event": event, + "data": data, + }]) + `; + expect(formatter.format(input).trim()).to.equal(expected.trim()); + }); }); diff --git a/src/formatters/MultiLineItemFormatter.ts b/src/formatters/MultiLineItemFormatter.ts index 17faef5..7f049a3 100644 --- a/src/formatters/MultiLineItemFormatter.ts +++ b/src/formatters/MultiLineItemFormatter.ts @@ -33,7 +33,9 @@ export class MultiLineItemFormatter { //is NOT array like `[[ ...\n ]]`, or `[{ ...\n }]`) !this.isMatchingDoubleArrayOrArrayCurly(tokens, i) && //Don't reformat if the opening bracket or curly is on the same line as 'return' - !this.isReturnArrayOrCurlyOnSameLine(tokens, i) + !this.isReturnArrayOrCurlyOnSameLine(tokens, i) && + //Don't reformat if there are multiple open/close pairs that are unbalanced + !this.isMultiUnbalancedOpenClosePair(tokens, i) ) { tokens.splice(i + 1, 0, { kind: TokenKind.Newline, @@ -79,6 +81,38 @@ export class MultiLineItemFormatter { return false; } + /** + * Detects when a line has both unbalanced square brackets and curly braces, + * which indicates a pattern like `[m, {` that should not be reformatted. + * Scans tokens from the given index until a newline is found. + * @param tokens The array of tokens to scan. + * @param currentIndex The index to start scanning from. + * @returns {boolean} True if both curly and square brackets are unbalanced before a newline; otherwise, false. + */ + public isMultiUnbalancedOpenClosePair(tokens: Token[], currentIndex: number) { + let curlyOpenCount = 0; + let squareOpenCount = 0; + + for (let i = currentIndex; i < tokens.length; i++) { + let token = tokens[i]; + if (token.kind === TokenKind.Newline) { + break; + } + + if (token.kind === TokenKind.LeftCurlyBrace) { + curlyOpenCount++; + } else if (token.kind === TokenKind.RightCurlyBrace) { + curlyOpenCount--; + } else if (token.kind === TokenKind.LeftSquareBracket) { + squareOpenCount++; + } else if (token.kind === TokenKind.RightSquareBracket) { + squareOpenCount--; + } + } + + return curlyOpenCount > 0 && squareOpenCount > 0; + } + public isMatchingDoubleArrayOrArrayCurly(tokens: Token[], currentIndex: number) { let token = tokens[currentIndex]; let nextNonWhitespaceToken = util.getNextNonWhitespaceToken(tokens, currentIndex, true); From 68d3b2706e06339952d6ae7bbb796c71f8e6ea3a Mon Sep 17 00:00:00 2001 From: Rob Reed Date: Sat, 29 Nov 2025 16:55:50 -0800 Subject: [PATCH 2/4] Fix test case for isMatchingDoubleArrayOrArrayCurly (remove istanbul) --- src/formatters/MultiLineItemFormatter.spec.ts | 21 +++++++++++++++++++ src/formatters/MultiLineItemFormatter.ts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/formatters/MultiLineItemFormatter.spec.ts b/src/formatters/MultiLineItemFormatter.spec.ts index e4a5cea..817ee5d 100644 --- a/src/formatters/MultiLineItemFormatter.spec.ts +++ b/src/formatters/MultiLineItemFormatter.spec.ts @@ -16,6 +16,27 @@ describe('MultiLineItemFormatter', () => { expect(formatter.format(inputSameLine).trim()).to.equal(expectedSameLine.trim()); }); + it('preserves isMatchingDoubleArrayOrArrayCurly [[ .. ]]', () => { + const input = undent` + [[1, 2, 3] + ]`; + const expected = undent` + [ + [1, 2, 3] + ]`; + expect(formatter.format(input).trim()).to.equal(expected.trim()); + }); + it('preserves isMatchingDoubleArrayOrArrayCurly [{ .. }]', () => { + const input = undent` + [{1, 2, 3} + ]`; + const expected = undent` + [ + { 1, 2, 3 } + ]`; + expect(formatter.format(input).trim()).to.equal(expected.trim()); + }); + it('preserves return object on same line (multi-line content)', () => { const input = `sub foo()\n return {\n a: 1\n }\nend sub`; const expected = `sub foo()\n return {\n a: 1\n }\nend sub`; diff --git a/src/formatters/MultiLineItemFormatter.ts b/src/formatters/MultiLineItemFormatter.ts index 7f049a3..d53540a 100644 --- a/src/formatters/MultiLineItemFormatter.ts +++ b/src/formatters/MultiLineItemFormatter.ts @@ -128,11 +128,11 @@ export class MultiLineItemFormatter { let closingToken = util.getClosingToken(tokens, currentIndex, TokenKind.LeftSquareBracket, TokenKind.RightSquareBracket); //look at the previous token let previous = closingToken && util.getPreviousNonWhitespaceToken(tokens, tokens.indexOf(closingToken), true); - /* istanbul ignore else (because I can't figure out how to make this happen but I think it's still necessary) */ if (previous && (previous.kind === TokenKind.RightSquareBracket || previous.kind === TokenKind.RightCurlyBrace)) { return true; } } + return false; } /** From 90f97ed8bcdc82de97488c53a5226a57d28c4005 Mon Sep 17 00:00:00 2001 From: Rob Reed Date: Sat, 29 Nov 2025 16:57:39 -0800 Subject: [PATCH 3/4] [GHA] disable windows building (slow) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d2208c..85cf8e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: NODE_OPTIONS: "--max-old-space-size=4096" strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@master - uses: actions/setup-node@master From 8211cb1a6a3341ba9b0533ed049b5c0742faf9d0 Mon Sep 17 00:00:00 2001 From: Rob Reed Date: Sat, 29 Nov 2025 17:01:32 -0800 Subject: [PATCH 4/4] [GHA] better handle canceling old builds. --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 85cf8e7..049b905 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,14 @@ on: - master pull_request: +concurrency: + # We use the pull request number to ensure that each PR has a unique concurrency group. This prevents different PRs from interfering + # with each other, especially when multiple PRs originate from the same branch. If the pull request number is not available, we fall + # back to using the branch name (`github.head_ref`) or the full reference (`github.ref`), ensuring uniqueness. + # + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }} + cancel-in-progress: true + jobs: ci: runs-on: ${{ matrix.os }}