From ac7321394ca4be26f63ebb92a6063f47486b1563 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 7 Mar 2024 15:00:30 -0500 Subject: [PATCH] Add `insertSpaceAfterConditionalCompileSymbol`, fix conditional compile formatting (#87) * Fix formatting for conditional compile with spaces after # * brighterscript@0.65.25 * Fix code coverage --- package-lock.json | 95 ++++++++------ package.json | 2 +- src/Formatter.spec.ts | 123 ++++++++++++++++++ src/FormattingOptions.ts | 11 +- src/constants.ts | 9 ++ .../CompositeKeywordFormatter.spec.ts | 18 ++- src/formatters/CompositeKeywordFormatter.ts | 5 +- src/formatters/InteriorWhitespaceFormatter.ts | 11 +- 8 files changed, 226 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f2d236..a8499aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.6.41", "license": "MIT", "dependencies": { - "brighterscript": "^0.65.23", + "brighterscript": "^0.65.25", "glob-all": "^3.3.0", "jsonc-parser": "^3.0.0", "source-map": "^0.7.3", @@ -572,14 +572,6 @@ "node": ">=6" } }, - "node_modules/@postman/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/@postman/tunnel-agent": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", @@ -1164,9 +1156,9 @@ } }, "node_modules/brighterscript": { - "version": "0.65.23", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.23.tgz", - "integrity": "sha512-oIekpbevsdZoQR93sarPCzeCuMBCyq6x4UdVO9D63ip8Yqytlh4dtcwrbPNIGXCaJw1KBdLhU13+VcbSrn9oUg==", + "version": "0.65.25", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.25.tgz", + "integrity": "sha512-sezfScxcXZfzEJ702HCMjfPsIH/x3mfLAFDObGU+xAC2E8m/zxrPOHKlD9Qpmdvd7/j4TgsujhiRQ1tnk8BzGA==", "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -1190,7 +1182,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.11.3", + "roku-deploy": "^3.12.0", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -1222,6 +1214,14 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" }, + "node_modules/brighterscript/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/brighterscript/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -4445,9 +4445,9 @@ } }, "node_modules/roku-deploy": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.3.tgz", - "integrity": "sha512-vHb/YL45LWrD+hOAGO9GGhZW5GE6W5I/bSGLCO/JgyqhvRq/MoKdeFuEUgrztDhaKgODQIjGj7/DJaUO0Vx+IA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.0.tgz", + "integrity": "sha512-YiCZeQ+sEmFW9ZfXtMNH+/CBSHQ5deNZYWONM+s6gCEQsrz7kCMFPj5YEdgfqW+d2b8G1ve9GELHcSt2FsfM8g==", "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -4487,6 +4487,14 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" }, + "node_modules/roku-deploy/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5018,9 +5026,9 @@ "dev": true }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "engines": { "node": ">= 4.0.0" } @@ -5110,9 +5118,9 @@ } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz", - "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" }, "node_modules/vscode-languageserver-types": { "version": "3.16.0", @@ -5745,13 +5753,6 @@ "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - } } }, "@postman/tunnel-agent": { @@ -6195,9 +6196,9 @@ } }, "brighterscript": { - "version": "0.65.23", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.23.tgz", - "integrity": "sha512-oIekpbevsdZoQR93sarPCzeCuMBCyq6x4UdVO9D63ip8Yqytlh4dtcwrbPNIGXCaJw1KBdLhU13+VcbSrn9oUg==", + "version": "0.65.25", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.25.tgz", + "integrity": "sha512-sezfScxcXZfzEJ702HCMjfPsIH/x3mfLAFDObGU+xAC2E8m/zxrPOHKlD9Qpmdvd7/j4TgsujhiRQ1tnk8BzGA==", "requires": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -6221,7 +6222,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.11.3", + "roku-deploy": "^3.12.0", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -6247,6 +6248,11 @@ "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -8689,9 +8695,9 @@ } }, "roku-deploy": { - "version": "3.11.3", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.3.tgz", - "integrity": "sha512-vHb/YL45LWrD+hOAGO9GGhZW5GE6W5I/bSGLCO/JgyqhvRq/MoKdeFuEUgrztDhaKgODQIjGj7/DJaUO0Vx+IA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.0.tgz", + "integrity": "sha512-YiCZeQ+sEmFW9ZfXtMNH+/CBSHQ5deNZYWONM+s6gCEQsrz7kCMFPj5YEdgfqW+d2b8G1ve9GELHcSt2FsfM8g==", "requires": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -8724,6 +8730,11 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" } } }, @@ -9113,9 +9124,9 @@ "dev": true }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" }, "uri-js": { "version": "4.4.1", @@ -9191,9 +9202,9 @@ } }, "vscode-languageserver-textdocument": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.7.tgz", - "integrity": "sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" }, "vscode-languageserver-types": { "version": "3.16.0", diff --git a/package.json b/package.json index 35893fb..e5c4a87 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "brighterscript-formatter": "dist/cli.js" }, "dependencies": { - "brighterscript": "^0.65.23", + "brighterscript": "^0.65.25", "glob-all": "^3.3.0", "jsonc-parser": "^3.0.0", "source-map": "^0.7.3", diff --git a/src/Formatter.spec.ts b/src/Formatter.spec.ts index 675b278..6d6ed60 100644 --- a/src/Formatter.spec.ts +++ b/src/Formatter.spec.ts @@ -14,6 +14,32 @@ describe('Formatter', () => { }); describe('formatIndent', () => { + it('formats conditional compile items with spaces around the keywords', () => { + expect(formatter.format(undent` + sub Main(inputArguments as object) + #if true + print" one" + #else if true + print "two" + #else + print "three" + #end if + print "done" + end sub + `)).to.equal(undent` + sub Main(inputArguments as object) + #if true + print" one" + #else if true + print "two" + #else + print "three" + #end if + print "done" + end sub + `); + }); + it('formats with optional chaining operators', () => { formatEqualTrim(` sub setPoster() @@ -655,6 +681,103 @@ end sub`; }); }); + it('removes whitespace between conditional compile symbol and keyword', () => { + expect(formatter.format(undent` + sub Main(inputArguments as object) + #\t const SOME_CONST = true + #\t if true + print" one" + #\t else if true + print "two" + #\t else + print "three" + #\t error + #\t error message 123 + #\t end if + print "done" + end sub + `, { insertSpaceAfterConditionalCompileSymbol: false })).to.equal(undent` + sub Main(inputArguments as object) + #const SOME_CONST = true + #if true + print" one" + #else if true + print "two" + #else + print "three" + #error + #error message 123 + #end if + print "done" + end sub + `); + + }); + + it('reduces to single space between conditional compile symbol and keyword', () => { + expect(formatter.format(undent` + sub Main(inputArguments as object) + #\t const SOME_CONST = true + #\t if true + print" one" + #\t else if true + print "two" + #\t else + print "three" + #\t error + #\t error message 123 + #\t end if + print "done" + end sub + `, { insertSpaceAfterConditionalCompileSymbol: true })).to.equal(undent` + sub Main(inputArguments as object) + # const SOME_CONST = true + # if true + print" one" + # else if true + print "two" + # else + print "three" + # error + # error message 123 + # end if + print "done" + end sub + `); + }); + + it('adds single space between conditional compile symbol and keyword', () => { + expect(formatter.format(undent` + sub Main(inputArguments as object) + #const SOME_CONST = true + #if true + print" one" + #else if true + print "two" + #else + print "three" + #error + #error message 123 + #end if + print "done" + end sub + `, { insertSpaceAfterConditionalCompileSymbol: true })).to.equal(undent` + sub Main(inputArguments as object) + # const SOME_CONST = true + # if true + print" one" + # else if true + print "two" + # else + print "three" + # error + # error message 123 + # end if + print "done" + end sub + `); + }); + it('removes space between empty parens', () => { formatEqual(`main( )`, `main()`); formatEqual(`main()`, `main()`); diff --git a/src/FormattingOptions.ts b/src/FormattingOptions.ts index 0b04408..0ebec3d 100644 --- a/src/FormattingOptions.ts +++ b/src/FormattingOptions.ts @@ -52,12 +52,14 @@ export interface FormattingOptions { */ typeCaseOverride?: Record; /** - * If true (the default), all whitespace between items is reduced to exactly 1 space character, + * If true (the default), all whitespace between items are reduced to exactly 1 space character, * and certain keywords and operators are padded with whitespace (i.e. `1+1` becomes `1 + 1`). * This is a catchall property that will also disable the following rules: * - insertSpaceBeforeFunctionParenthesis * - insertSpaceBetweenEmptyCurlyBraces * - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces + * - insertSpaceAfterConditionalCompileSymbol + * - insertSpaceBetweenAssociativeArrayLiteralKeyAndColon */ formatInteriorWhitespace?: boolean; /** @@ -72,6 +74,12 @@ export interface FormattingOptions { * @default false */ insertSpaceBetweenEmptyCurlyBraces?: boolean; + /** + * if true, conditional compile symbols will contain exactly 1 whitespace char (i.e. `# if true`) + * if false, ensure there is no whitespace between the `#` and the keyword (i.e. `#if true`) + * @default false + */ + insertSpaceAfterConditionalCompileSymbol?: boolean; /** * If true, ensure exactly 1 space after leading and before trailing curly braces * If false, REMOVE all whitespace after leading and before trailing curly braces (excluding beginning-of-line indentation spacing) @@ -115,6 +123,7 @@ export function normalizeOptions(options: FormattingOptions) { formatInteriorWhitespace: true, insertSpaceBeforeFunctionParenthesis: false, insertSpaceBetweenEmptyCurlyBraces: false, + insertSpaceAfterConditionalCompileSymbol: false, insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, insertSpaceBetweenAssociativeArrayLiteralKeyAndColon: false, formatMultiLineObjectsAndArrays: true, diff --git a/src/constants.ts b/src/constants.ts index 3981e4c..ad7f36a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -202,6 +202,15 @@ export const TypeTokens = [ TokenKind.Void ]; +export const ConditionalCompileTokenKinds = [ + TokenKind.HashConst, + TokenKind.HashElse, + TokenKind.HashElseIf, + TokenKind.HashEndIf, + TokenKind.HashError, + TokenKind.HashIf +]; + export const CompositeKeywordStartingWords = ['end', 'exit', 'else', '#end', '#else']; export const AllowedClassIdentifierKinds = [TokenKind.Identifier, ...AllowedLocalIdentifiers]; diff --git a/src/formatters/CompositeKeywordFormatter.spec.ts b/src/formatters/CompositeKeywordFormatter.spec.ts index 31436bb..f6ee003 100644 --- a/src/formatters/CompositeKeywordFormatter.spec.ts +++ b/src/formatters/CompositeKeywordFormatter.spec.ts @@ -26,10 +26,26 @@ describe('CompositeKeywordFormatter', () => { expect(parts[0]).to.equal('else'); expect(parts[1]).to.equal('if'); + }); + + it('works with conditional compile parts', () => { + let parts; + parts = Formatter['getCompositeKeywordParts']({ text: '#else if' } as any); expect(parts[0]).to.equal('#else'); expect(parts[1]).to.equal('if'); + + parts = Formatter['getCompositeKeywordParts']({ text: '#\t else if' } as any); + expect(parts[0]).to.equal('#\t else'); + expect(parts[1]).to.equal('if'); + + parts = Formatter['getCompositeKeywordParts']({ text: '#end if' } as any); + expect(parts[0]).to.equal('#end'); + expect(parts[1]).to.equal('if'); + + parts = Formatter['getCompositeKeywordParts']({ text: '#\t end if' } as any); + expect(parts[0]).to.equal('#\t end'); + expect(parts[1]).to.equal('if'); }); }); }); - diff --git a/src/formatters/CompositeKeywordFormatter.ts b/src/formatters/CompositeKeywordFormatter.ts index ae6b683..29a662d 100644 --- a/src/formatters/CompositeKeywordFormatter.ts +++ b/src/formatters/CompositeKeywordFormatter.ts @@ -66,11 +66,12 @@ export class CompositeKeywordFormatter { private getCompositeKeywordParts(token: Token) { let lowerValue = token.text.toLowerCase(); + let match: RegExpExecArray | null; //split the parts of the token, but retain their case if (lowerValue.startsWith('end')) { return [token.text.substring(0, 3), token.text.substring(3).trim()]; - } else if (lowerValue.startsWith('#else')) { - return [token.text.substring(0, 5), token.text.substring(5).trim()]; + } else if ((match = /^(#\s*(?:else|end))\s*(if)/i.exec(token.text))) { + return match.slice(1) as [string, string]; } else { return [token.text.substring(0, 4), token.text.substring(4).trim()]; } diff --git a/src/formatters/InteriorWhitespaceFormatter.ts b/src/formatters/InteriorWhitespaceFormatter.ts index d72dd0a..963e589 100644 --- a/src/formatters/InteriorWhitespaceFormatter.ts +++ b/src/formatters/InteriorWhitespaceFormatter.ts @@ -1,7 +1,7 @@ import type { AALiteralExpression, AAMemberExpression, Parser, Token } from 'brighterscript'; import { createVisitor, WalkMode, TokenKind } from 'brighterscript'; import type { TokenWithStartIndex } from '../constants'; -import { TokensBeforeNegativeNumericLiteral, NumericLiteralTokenKinds } from '../constants'; +import { TokensBeforeNegativeNumericLiteral, NumericLiteralTokenKinds, ConditionalCompileTokenKinds } from '../constants'; import type { FormattingOptions } from '../FormattingOptions'; import { util } from '../util'; @@ -82,6 +82,15 @@ export class InteriorWhitespaceFormatter { if (token.kind === TokenKind.Whitespace && isPastFirstTokenOfLine === false) { continue; } + + //normalize whitespace following conditional compile symbol #if, #else, #elseif, etc... + if (ConditionalCompileTokenKinds.includes(token.kind)) { + if (options.insertSpaceAfterConditionalCompileSymbol) { + token.text = token.text.replace(/^#\s*/, '# '); + } else { + token.text = token.text.replace(/^#\s*/, '#'); + } + } isPastFirstTokenOfLine = true; if (token.kind === TokenKind.Whitespace) { //force token to be exactly 1 space