@@ -12,30 +12,25 @@ export enum IdentifierKind {
12
12
RegularWithRequiredQuotes = "RegularWithRequiredQuotes" ,
13
13
}
14
14
15
- // Assuming the text is a quoted identifier, finds the quotes that enclose the identifier.
16
- // Otherwise returns undefined.
17
- export function findQuotedIdentifierQuotes ( text : string , index : number ) : StringUtils . FoundQuotes | undefined {
18
- if ( text [ index ] !== "#" ) {
19
- return undefined ;
20
- }
21
-
22
- return StringUtils . findQuotes ( text , index + 1 ) ;
15
+ export interface IdentifierUtilsOptions {
16
+ readonly allowGeneralizedIdentifier ?: boolean ;
17
+ readonly allowTrailingPeriod ?: boolean ;
23
18
}
24
19
25
- export function getAllowedIdentifiers ( text : string , isGeneralizedIdentifierAllowed : boolean ) : ReadonlyArray < string > {
26
- const quotedAndUnquoted : TQuotedAndUnquoted | undefined = getQuotedAndUnquoted ( text ) ;
20
+ export function getAllowedIdentifiers ( text : string , options ?: IdentifierUtilsOptions ) : ReadonlyArray < string > {
21
+ const allowGeneralizedIdentifier : boolean =
22
+ options ?. allowGeneralizedIdentifier ?? DefaultallowGeneralizedIdentifier ;
23
+
24
+ const quotedAndUnquoted : TQuotedAndUnquoted | undefined = getQuotedAndUnquoted ( text , options ) ;
27
25
28
26
if ( quotedAndUnquoted === undefined ) {
29
27
return [ ] ;
30
28
}
31
29
32
30
switch ( quotedAndUnquoted . identifierKind ) {
33
31
case IdentifierKind . Generalized :
34
- quotedAndUnquoted . withoutQuotes ;
35
-
36
- return isGeneralizedIdentifierAllowed
37
- ? [ quotedAndUnquoted . withQuotes , quotedAndUnquoted . withoutQuotes ]
38
- : [ ] ;
32
+ case IdentifierKind . GeneralizedWithQuotes :
33
+ return allowGeneralizedIdentifier ? [ quotedAndUnquoted . withQuotes , quotedAndUnquoted . withoutQuotes ] : [ ] ;
39
34
40
35
case IdentifierKind . Invalid :
41
36
return [ ] ;
@@ -57,23 +52,37 @@ export function getAllowedIdentifiers(text: string, isGeneralizedIdentifierAllow
57
52
// Determines what kind of identifier the text is.
58
53
// It's possible that the text is a partially completed identifier,
59
54
// which is why we have the `allowTrailingPeriod` parameter.
60
- export function getIdentifierKind ( text : string , allowTrailingPeriod : boolean ) : IdentifierKind {
61
- if ( isRegularIdentifier ( text , allowTrailingPeriod ) ) {
55
+ export function getIdentifierKind ( text : string , options ?: IdentifierUtilsOptions ) : IdentifierKind {
56
+ const allowGeneralizedIdentifier : boolean =
57
+ options ?. allowGeneralizedIdentifier ?? DefaultallowGeneralizedIdentifier ;
58
+
59
+ if ( isRegularIdentifier ( text , options ) ) {
62
60
return IdentifierKind . Regular ;
63
- } else if ( isQuotedIdentifier ( text ) ) {
64
- if ( isRegularIdentifier )
65
- return isRegularIdentifier ( text . slice ( 2 , - 1 ) , false )
66
- ? IdentifierKind . RegularWithQuotes
67
- : IdentifierKind . RegularWithRequiredQuotes ;
68
- } else if ( isGeneralizedIdentifier ( text ) ) {
61
+ } else if ( allowGeneralizedIdentifier && isGeneralizedIdentifier ( text ) ) {
69
62
return IdentifierKind . Generalized ;
63
+ }
64
+ // If the identifier is quoted it's either:
65
+ // - a regular identifier with quotes,
66
+ // - a generalized identifier with quotes,
67
+ else if ( isQuotedIdentifier ( text ) ) {
68
+ const stripped : string = stripQuotes ( text ) ;
69
+
70
+ if ( isRegularIdentifier ( stripped , options ) ) {
71
+ return IdentifierKind . RegularWithQuotes ;
72
+ } else if ( isGeneralizedIdentifier ( stripped ) && allowGeneralizedIdentifier ) {
73
+ return IdentifierKind . GeneralizedWithQuotes ;
74
+ } else {
75
+ return IdentifierKind . RegularWithRequiredQuotes ;
76
+ }
70
77
} else {
71
78
return IdentifierKind . Invalid ;
72
79
}
73
80
}
74
81
75
- // Assuming the text is an identifier, returns the length of the identifier.
76
- export function getIdentifierLength ( text : string , index : number , allowTrailingPeriod : boolean ) : number | undefined {
82
+ // I'd prefer if this was internal, but it's used by the lexer so it's marked as public.
83
+ // Returns the length of the identifier starting at the given index.
84
+ export function getIdentifierLength ( text : string , index : number , options ?: IdentifierUtilsOptions ) : number | undefined {
85
+ const allowTrailingPeriod : boolean = options ?. allowTrailingPeriod ?? DefaultAllowTrailingPeriod ;
77
86
const startingIndex : number = index ;
78
87
const textLength : number = text . length ;
79
88
@@ -153,8 +162,69 @@ export function getIdentifierLength(text: string, index: number, allowTrailingPe
153
162
return index !== startingIndex ? index - startingIndex : undefined ;
154
163
}
155
164
165
+ // Removes the quotes from a quoted identifier if possible.
166
+ export function getNormalizedIdentifier (
167
+ text : string ,
168
+ options ?: IdentifierUtilsOptions ,
169
+ ) : Result < string , CommonError . InvariantError > {
170
+ const allowGeneralizedIdentifier : boolean =
171
+ options ?. allowGeneralizedIdentifier ?? DefaultallowGeneralizedIdentifier ;
172
+
173
+ const quotedAndUnquoted : TQuotedAndUnquoted = getQuotedAndUnquoted ( text , options ) ;
174
+
175
+ if ( quotedAndUnquoted . identifierKind === IdentifierKind . Invalid ) {
176
+ return ResultUtils . error ( new CommonError . InvariantError ( `The text "${ text } " is not a valid identifier.` ) ) ;
177
+ }
178
+
179
+ // Validate a generalized identifier is allowed in this context.
180
+ if ( quotedAndUnquoted . identifierKind === IdentifierKind . Generalized && ! allowGeneralizedIdentifier ) {
181
+ return ResultUtils . error (
182
+ new CommonError . InvariantError (
183
+ `The text "${ text } " is a generalized identifier, but it is not allowed in this context.` ,
184
+ ) ,
185
+ ) ;
186
+ }
187
+
188
+ // Prefer without quotes if it exists.
189
+ return ResultUtils . ok ( quotedAndUnquoted . withoutQuotes ?? quotedAndUnquoted . withQuotes ) ;
190
+ }
191
+
192
+ interface IQuotedAndUnquoted <
193
+ TKind extends IdentifierKind ,
194
+ TWithQuotes extends string | undefined ,
195
+ TWithoutQuotes extends string | undefined ,
196
+ > {
197
+ readonly identifierKind : TKind ;
198
+ readonly withQuotes : TWithQuotes ;
199
+ readonly withoutQuotes : TWithoutQuotes ;
200
+ }
201
+
202
+ type TQuotedAndUnquoted =
203
+ | IQuotedAndUnquoted < IdentifierKind . Generalized , string , string >
204
+ | IQuotedAndUnquoted < IdentifierKind . GeneralizedWithQuotes , string , string >
205
+ | IQuotedAndUnquoted < IdentifierKind . Invalid , undefined , undefined >
206
+ | IQuotedAndUnquoted < IdentifierKind . RegularWithQuotes , string , string >
207
+ | IQuotedAndUnquoted < IdentifierKind . RegularWithRequiredQuotes , string , undefined >
208
+ | IQuotedAndUnquoted < IdentifierKind . Regular , string , string > ;
209
+
210
+ const enum IdentifierRegexpState {
211
+ Done = "Done" ,
212
+ RegularIdentifier = "RegularIdentifier" ,
213
+ Start = "Start" ,
214
+ }
215
+
216
+ // Assuming the text is a quoted identifier, finds the quotes that enclose the identifier.
217
+ // Otherwise returns undefined.
218
+ function findQuotedIdentifierQuotes ( text : string , index : number ) : StringUtils . FoundQuotes | undefined {
219
+ if ( text [ index ] !== "#" ) {
220
+ return undefined ;
221
+ }
222
+
223
+ return StringUtils . findQuotes ( text , index + 1 ) ;
224
+ }
225
+
156
226
// Assuming the text is a generalized identifier, returns the length of the identifier.
157
- export function getGeneralizedIdentifierLength ( text : string , index : number ) : number | undefined {
227
+ function getGeneralizedIdentifierLength ( text : string , index : number ) : number | undefined {
158
228
const startingIndex : number = index ;
159
229
const textLength : number = text . length ;
160
230
@@ -195,50 +265,21 @@ export function getGeneralizedIdentifierLength(text: string, index: number): num
195
265
return index !== startingIndex ? index - startingIndex : undefined ;
196
266
}
197
267
198
- export function isGeneralizedIdentifier ( text : string ) : boolean {
199
- return getGeneralizedIdentifierLength ( text , 0 ) === text . length ;
200
- }
201
-
202
- export function isRegularIdentifier ( text : string , allowTrailingPeriod : boolean ) : boolean {
203
- return getIdentifierLength ( text , 0 , allowTrailingPeriod ) === text . length ;
204
- }
205
-
206
- export function isQuotedIdentifier ( text : string ) : boolean {
207
- return findQuotedIdentifierQuotes ( text , 0 ) !== undefined ;
208
- }
209
-
210
- // Removes the quotes from a quoted identifier if possible.
211
- export function getNormalizedIdentifier (
212
- text : string ,
213
- isGeneralizedIdentifierAllowed : boolean ,
214
- ) : Result < string , CommonError . InvariantError > {
215
- const quotedAndUnquoted : TQuotedAndUnquoted = getQuotedAndUnquoted ( text ) ;
216
-
217
- if ( quotedAndUnquoted . identifierKind === IdentifierKind . Invalid ) {
218
- return ResultUtils . error ( new CommonError . InvariantError ( `The text "${ text } " is not a valid identifier.` ) ) ;
219
- }
220
-
221
- // Validate a generalized identifier is allowed in this context.
222
- if ( quotedAndUnquoted . identifierKind === IdentifierKind . Generalized && ! isGeneralizedIdentifierAllowed ) {
223
- return ResultUtils . error (
224
- new CommonError . InvariantError (
225
- `The text "${ text } " is a generalized identifier, but it is not allowed in this context.` ,
226
- ) ,
227
- ) ;
228
- }
229
-
230
- // Prefer without quotes if it exists.
231
- return ResultUtils . ok ( quotedAndUnquoted . withoutQuotes ?? quotedAndUnquoted . withQuotes ) ;
232
- }
233
-
234
- function getQuotedAndUnquoted ( text : string ) : TQuotedAndUnquoted {
235
- const identifierKind : IdentifierKind = getIdentifierKind ( text , /* allowTrailingPeriod */ false ) ;
268
+ function getQuotedAndUnquoted ( text : string , options ?: IdentifierUtilsOptions ) : TQuotedAndUnquoted {
269
+ const identifierKind : IdentifierKind = getIdentifierKind ( text , options ) ;
236
270
237
271
switch ( identifierKind ) {
238
272
case IdentifierKind . Generalized :
239
273
return {
240
274
identifierKind,
241
- withoutQuotes : insertQuotes ( text ) ,
275
+ withoutQuotes : text ,
276
+ withQuotes : insertQuotes ( text ) ,
277
+ } ;
278
+
279
+ case IdentifierKind . GeneralizedWithQuotes :
280
+ return {
281
+ identifierKind,
282
+ withoutQuotes : stripQuotes ( text ) ,
242
283
withQuotes : text ,
243
284
} ;
244
285
@@ -275,38 +316,25 @@ function getQuotedAndUnquoted(text: string): TQuotedAndUnquoted {
275
316
}
276
317
}
277
318
278
- interface IQuotedAndUnquoted <
279
- TKind extends IdentifierKind ,
280
- TWithQuotes extends string | undefined ,
281
- TWithoutQuotes extends string | undefined ,
282
- > {
283
- readonly identifierKind : TKind ;
284
- readonly withQuotes : TWithQuotes ;
285
- readonly withoutQuotes : TWithoutQuotes ;
319
+ function insertQuotes ( text : string ) : string {
320
+ return `#"${ text } "` ;
286
321
}
287
322
288
- type TQuotedAndUnquoted =
289
- | IQuotedAndUnquoted < IdentifierKind . Generalized , string , string >
290
- | IQuotedAndUnquoted < IdentifierKind . Invalid , undefined , undefined >
291
- | IQuotedAndUnquoted < IdentifierKind . RegularWithQuotes , string , string >
292
- | IQuotedAndUnquoted < IdentifierKind . RegularWithRequiredQuotes , string , undefined >
293
- | IQuotedAndUnquoted < IdentifierKind . Regular , string , string > ;
323
+ function isGeneralizedIdentifier ( text : string ) : boolean {
324
+ return text . length > 0 && getGeneralizedIdentifierLength ( text , 0 ) === text . length ;
325
+ }
294
326
295
- const enum IdentifierRegexpState {
296
- Done = "Done" ,
297
- RegularIdentifier = "RegularIdentifier" ,
298
- Start = "Start" ,
327
+ function isRegularIdentifier ( text : string , options ?: IdentifierUtilsOptions ) : boolean {
328
+ return text . length > 0 && getIdentifierLength ( text , 0 , options ) === text . length ;
299
329
}
300
330
301
- function insertQuotes ( text : string ) : string {
302
- return `#" ${ text } "` ;
331
+ function isQuotedIdentifier ( text : string ) : boolean {
332
+ return findQuotedIdentifierQuotes ( text , 0 ) !== undefined ;
303
333
}
304
334
305
335
function stripQuotes ( text : string ) : string {
306
336
return text . slice ( 2 , - 1 ) ;
307
337
}
308
338
309
- interface IdentifierUtilsOptions {
310
- readonly allowTrailingPeriod ?: boolean ;
311
- readonly isGeneralizedIdentifierAllowed ?: boolean ;
312
- }
339
+ const DefaultAllowTrailingPeriod : boolean = false ;
340
+ const DefaultallowGeneralizedIdentifier : boolean = false ;
0 commit comments