Skip to content

Commit 7a18082

Browse files
committed
Add specificKeywordCaseOverride and specificTypeCaseOverride
1 parent 1fe1bb5 commit 7a18082

File tree

5 files changed

+128
-3
lines changed

5 files changed

+128
-3
lines changed

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ bsfmt "source/**/*.brs" "!**/roku_modules/*.*"
6868
| noBsfmt |`boolean` | `false` | Don't read a bsfmt.json file |
6969
| bsfmtPath |`string` | `undefined` | Use a specified path to bsfmt.json instead of the default |
7070
||||
71-
All boolean, string, and integer [`bsfmt.json`](#bsfmtjson-options) options are supported as well. Complex options such as `keywordCaseOverride` or `typeCaseOverride` are not currently supported via the CLI and should be provided in the `bsfmt.json` or through the node API. Feel free to [open an issue](https://github.com/rokucommunity/brighterscript-formatter/issues/new) if you would like to see support for these options via the CLI.
71+
All boolean, string, and integer [`bsfmt.json`](#bsfmtjson-options) options are supported as well. Complex options such as `keywordCaseOverride`, `typeCaseOverride`, `specificKeywordCaseOverride`, or `specificTypeCaseOverride` are not currently supported via the CLI and should be provided in the `bsfmt.json` or through the node API. Feel free to [open an issue](https://github.com/rokucommunity/brighterscript-formatter/issues/new) if you would like to see support for these options via the CLI.
7272

7373

7474

@@ -83,7 +83,9 @@ All boolean, string, and integer [`bsfmt.json`](#bsfmtjson-options) options are
8383
|compositeKeywords| `"split", "combine", "original"`| `"split"` | Forces all composite keywords (i.e. `elseif`, `endwhile`, etc...) to be consistent. If `"split"`, they are split into their alternatives (`else if`, `end while`). If `"combine"`', they are combined (`elseif`, `endwhile`). If `"original"` or falsey, they are not modified. |
8484
|removeTrailingWhiteSpace|`boolean`|`true`| Remove (or don't remove) trailing whitespace at the end of each line |
8585
|[keywordCaseOverride](#keywordCaseOverride)| `object`| `undefined`| Provides a way to override keyword case at the individual TokenType level|
86-
|[typeCaseOverride](#typeCaseOverride)|`object`|`undefined`| Provides a way to override type keyword case at the individual TokenType level.Types are defined as keywords that are preceded by an `as` token.|
86+
|[typeCaseOverride](#typeCaseOverride)|`object`|`undefined`| Provides a way to override type keyword case at the individual TokenType level. Types are defined as keywords that are preceded by an `as` token.|
87+
|[specificKeywordCaseOverride](#specificKeywordCaseOverride)| `object`| `undefined`| Provides a way to override keyword case with a specific string at the individual keyword level|
88+
|[specificTypeCaseOverride](#specificTypeCaseOverride)|`object`|`undefined`| Provides a way to override type keyword case with a specific string at the individual keyword level. Types are defined as keywords that are preceded by an `as` token. This accepts specific casing strings rather than case options like "upper" or "lower".|
8789
|formatInteriorWhitespace|`boolean`|`true`| All whitespace between items is reduced to exactly 1 space character and certain keywords and operators are padded with whitespace. This is a catchall property that will also disable the following rules: `insertSpaceBeforeFunctionParenthesis`, `insertSpaceBetweenEmptyCurlyBraces`, `insertSpaceAroundParameterAssignment`, `insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces`|
8890
|insertSpaceBeforeFunctionParenthesis|`boolean`|`false`| If true, a space is inserted to the left of an opening function declaration parenthesis. (i.e. `function main ()` or `function ()`). If false, all spacing is removed (i.e. `function main()` or `function()`).|
8991
|insertSpaceBetweenEmptyCurlyBraces|`boolean`|`false`| If true, empty curly braces will contain exactly 1 whitespace char (i.e. `{ }`). If false, there will be zero whitespace chars between empty curly braces (i.e. `{}`) |
@@ -128,6 +130,33 @@ For more flexibility in how to format the case of types, you can specify the cas
128130

129131
A type is any token found directly after an `as` keyword.
130132

133+
### specificKeywordCaseOverride
134+
For more flexibility in how to format the case of keywords, you can specify the case value preference for each individual keyword. Here's an example:
135+
136+
```js
137+
{
138+
"specificKeywordCaseOverride": {
139+
"longinteger": "longInteger",
140+
"endif": "EndIF"
141+
}
142+
}
143+
```
144+
145+
The full list of keywords detected by this option can be found [here](https://github.com/rokucommunity/brighterscript-formatter/blob/095f9dc5ec418d46d3ea6197712f5d11f71d922f/src/Formatter.ts#L1145).
146+
147+
### specificTypeCaseOverride
148+
For more flexibility in how to format the case of types, you can specify the case value preference for each individual type. Here's an example:
149+
150+
```js
151+
{
152+
"specificTypeCaseOverride": {
153+
"longinteger": "LongInteger"
154+
}
155+
}
156+
```
157+
158+
A type is any token found directly after an `as` keyword.
159+
131160
## Library
132161

133162
### General usage

bsfmt.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858
"default": true,
5959
"description": "Removes all trailing whitespace at the end of each line."
6060
},
61+
"specificKeywordCaseOverride": {
62+
"type": "object",
63+
"description": "Allows overriding specific keywords at the individual keyword level.\nExample {\"longinteger\": \"LongInteger\"} would make longinteger LongInteger"
64+
},
65+
"specificTypeCaseOverride": {
66+
"type": "object",
67+
"description": "Allows overriding specific types at the individual type level.\nExample {\"longinteger\": \"longInteger\"} would make longinteger longInteger"
68+
},
6169
"keywordCaseOverride": {
6270
"type": "object",
6371
"description": "Allows overriding case at the individual keyword level.\nExample {\"string\": \"title\"} would make string always lower case regardless of keywordCase",

src/FormattingOptions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ export interface FormattingOptions {
5151
* Types are defined as keywords that are preceded by an `as` token.
5252
*/
5353
typeCaseOverride?: Record<string, FormattingOptions['keywordCase']>;
54+
/**
55+
* Provides a way to override keyword case with a specific string.
56+
* The key should be the lowercased keyword, and the value should be the desired casing.
57+
* e.g. { "longinteger": "LongInteger" }
58+
*/
59+
specificKeywordCaseOverride?: Record<string, string>;
60+
/**
61+
* Provides a way to override type keyword case with a specific string.
62+
* The key should be the lowercased keyword, and the value should be the desired casing.
63+
* e.g. { "longinteger": "LongInteger" }
64+
*/
65+
specificTypeCaseOverride?: Record<string, string>;
5466
/**
5567
* If true (the default), all whitespace between items are reduced to exactly 1 space character,
5668
* and certain keywords and operators are padded with whitespace (i.e. `1+1` becomes `1 + 1`).

src/formatters/KeywordCaseFormatter.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { expect } from 'chai';
22
import { KeywordCaseFormatter } from './KeywordCaseFormatter';
3+
import { Formatter as MainFormatter } from '../Formatter';
4+
import { undent } from 'undent';
35

46
describe('KeywordCaseFormatter', () => {
57
let Formatter: KeywordCaseFormatter;
@@ -22,4 +24,61 @@ describe('KeywordCaseFormatter', () => {
2224
expect(Formatter['upperCaseLetter']('hello', 5)).to.equal('hello');
2325
});
2426
});
27+
28+
describe('specificCaseOverride', () => {
29+
let formatter: MainFormatter;
30+
beforeEach(() => {
31+
formatter = new MainFormatter();
32+
});
33+
34+
it('specificKeywordCaseOverride', () => {
35+
const input = undent`sub Main() print "hello" end sub`;
36+
const expected = undent`sub Main() PRINT "hello" endSub`;
37+
expect(formatter.format(input, {
38+
keywordCase: 'lower',
39+
specificKeywordCaseOverride: {
40+
endsub: 'endSub',
41+
print: 'PRINT'
42+
}
43+
})).to.equal(expected);
44+
});
45+
46+
it('specificTypeCaseOverride', () => {
47+
const input = undent`sub Main(a as integer)`;
48+
const expected = undent`sub Main(a as Integer)`;
49+
expect(formatter.format(input, {
50+
typeCase: 'lower',
51+
specificTypeCaseOverride: {
52+
integer: 'Integer'
53+
}
54+
})).to.equal(expected);
55+
});
56+
57+
it('specificKeywordCaseOverride and specificTypeCaseOverride using different cases', () => {
58+
const input = undent`sub longinteger(a as longinteger)`;
59+
const expected = undent`sub LongInteger(a as longInteger)`;
60+
expect(formatter.format(input, {
61+
typeCase: 'lower',
62+
specificKeywordCaseOverride: {
63+
longinteger: 'LongInteger'
64+
},
65+
specificTypeCaseOverride: {
66+
longinteger: 'longInteger'
67+
}
68+
})).to.equal(expected);
69+
});
70+
71+
it('specificKeywordCaseOverride and specificTypeCaseOverride using invalid values (must match the original keyword)', () => {
72+
const input = undent`sub longinteger(a as longinteger)`;
73+
expect(formatter.format(input, {
74+
typeCase: 'lower',
75+
specificKeywordCaseOverride: {
76+
longinteger: 'integer'
77+
},
78+
specificTypeCaseOverride: {
79+
longinteger: 'integer'
80+
}
81+
})).to.equal(input);
82+
});
83+
});
2584
});

src/formatters/KeywordCaseFormatter.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,37 @@ export class KeywordCaseFormatter {
1212

1313
//if this token is a keyword
1414
if (Keywords.includes(token.kind)) {
15-
1615
let keywordCase: FormattingOptions['keywordCase'];
1716
let lowerKind = token.kind.toLowerCase();
1817

1918
//a token is a type if it's preceded by an `as` token
2019
if (this.isType(tokens, token)) {
20+
21+
//if the token is a type, check for a specific override
22+
const specificTypeCaseOverride = options.specificTypeCaseOverride?.[lowerKind];
23+
if (specificTypeCaseOverride && specificTypeCaseOverride.toLowerCase() === lowerKind) {
24+
token.text = specificTypeCaseOverride;
25+
continue;
26+
}
27+
2128
//options.typeCase is always set to options.keywordCase when not provided
2229
keywordCase = options.typeCase;
30+
2331
//if this is an overridden type keyword, use that override instead
2432
if (options.typeCaseOverride && options.typeCaseOverride[lowerKind] !== undefined) {
2533
keywordCase = options.typeCaseOverride[lowerKind];
2634
}
2735
} else {
36+
//if the token is a keyword, check for a specific override
37+
const specificKeywordCaseOverride = options.specificKeywordCaseOverride?.[lowerKind];
38+
if (specificKeywordCaseOverride && specificKeywordCaseOverride.toLowerCase() === lowerKind) {
39+
token.text = specificKeywordCaseOverride;
40+
continue;
41+
}
42+
43+
//keywordCase is always set to options.keywordCase when not provided
2844
keywordCase = options.keywordCase;
45+
2946
//if this is an overridable keyword, use that override instead
3047
if (options.keywordCaseOverride && options.keywordCaseOverride[lowerKind] !== undefined) {
3148
keywordCase = options.keywordCaseOverride[lowerKind];

0 commit comments

Comments
 (0)