Skip to content

Commit 29ab4a6

Browse files
authored
Typing fixes for Pontoon TypeScript migration, fix #524 (#525)
* Better typing for @fluent/syntax * Align AST with fluent-rs to use Union types instead of inheritance. * Give input type hints to Visitor and Transformer * Stricter typing for @fluent/langneg * Expose option type of API The AST now aligns with the fluent-rs one, and less so with the reference AST. That's OK as it helps consumers of the library in types JS variants to use narrowing reliably.
1 parent 7cc1312 commit 29ab4a6

File tree

9 files changed

+146
-128
lines changed

9 files changed

+146
-128
lines changed

fluent-langneg/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fluent-langneg/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*
88
*/
99

10-
export {negotiateLanguages} from "./negotiate_languages";
10+
export {
11+
negotiateLanguages, NegotiateLanguagesOptions
12+
} from "./negotiate_languages";
1113
export {acceptedLanguages} from "./accepted_languages";
1214
export {filterMatches} from "./matches";

fluent-langneg/src/negotiate_languages.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {filterMatches} from "./matches";
22

3-
interface NegotiateLanguagesOptions {
3+
export interface NegotiateLanguagesOptions {
44
strategy?: "filtering" | "matching" | "lookup";
55
defaultLocale?: string;
66
}
@@ -49,8 +49,8 @@ interface NegotiateLanguagesOptions {
4949
* This strategy requires defaultLocale option to be set.
5050
*/
5151
export function negotiateLanguages(
52-
requestedLocales: Array<string>,
53-
availableLocales: Array<string>,
52+
requestedLocales: Readonly<Array<string>>,
53+
availableLocales: Readonly<Array<string>>,
5454
{
5555
strategy = "filtering",
5656
defaultLocale,

fluent-syntax/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fluent-syntax/src/ast.ts

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
*
77
*/
88
export abstract class BaseNode {
9-
public type = "BaseNode";
109
[name: string]: unknown;
1110

1211
equals(other: BaseNode, ignoredFields: Array<string> = ["span"]): boolean {
@@ -46,7 +45,7 @@ export abstract class BaseNode {
4645
return true;
4746
}
4847

49-
clone(): BaseNode {
48+
clone(): this {
5049
function visit(value: unknown): unknown {
5150
if (value instanceof BaseNode) {
5251
return value.clone();
@@ -79,7 +78,6 @@ function scalarsEqual(
7978
* Base class for AST nodes which can have Spans.
8079
*/
8180
export abstract class SyntaxNode extends BaseNode {
82-
public type = "SyntaxNode";
8381
public span?: Span;
8482

8583
addSpan(start: number, end: number): void {
@@ -89,21 +87,20 @@ export abstract class SyntaxNode extends BaseNode {
8987

9088
export class Resource extends SyntaxNode {
9189
public type = "Resource" as const;
92-
public body: Array<Entry | Junk>;
93-
constructor(body: Array<Entry | Junk> = []) {
90+
public body: Array<Entry>;
91+
constructor(body: Array<Entry> = []) {
9492
super();
9593
this.body = body;
9694
}
9795
}
9896

99-
/*
100-
* An abstract base class for useful elements of Resource.body.
101-
*/
102-
export abstract class Entry extends SyntaxNode {
103-
public type = "Entry";
104-
}
97+
export declare type Entry =
98+
Message |
99+
Term |
100+
Comments |
101+
Junk;
105102

106-
export class Message extends Entry {
103+
export class Message extends SyntaxNode {
107104
public type = "Message" as const;
108105
public id: Identifier;
109106
public value: Pattern | null;
@@ -124,7 +121,7 @@ export class Message extends Entry {
124121
}
125122
}
126123

127-
export class Term extends Entry {
124+
export class Term extends SyntaxNode {
128125
public type = "Term" as const;
129126
public id: Identifier;
130127
public value: Pattern;
@@ -155,14 +152,9 @@ export class Pattern extends SyntaxNode {
155152
}
156153
}
157154

158-
/*
159-
* An abstract base class for elements of Patterns.
160-
*/
161-
export abstract class PatternElement extends SyntaxNode {
162-
public type = "PatternElement";
163-
}
155+
export declare type PatternElement = TextElement | Placeable;
164156

165-
export class TextElement extends PatternElement {
157+
export class TextElement extends SyntaxNode {
166158
public type = "TextElement" as const;
167159
public value: string;
168160

@@ -172,7 +164,7 @@ export class TextElement extends PatternElement {
172164
}
173165
}
174166

175-
export class Placeable extends PatternElement {
167+
export class Placeable extends SyntaxNode {
176168
public type = "Placeable" as const;
177169
public expression: Expression;
178170

@@ -182,16 +174,21 @@ export class Placeable extends PatternElement {
182174
}
183175
}
184176

185-
/*
186-
* An abstract base class for expressions.
177+
/**
178+
* A subset of expressions which can be used as outside of Placeables.
187179
*/
188-
export abstract class Expression extends SyntaxNode {
189-
public type = "Expression";
190-
}
180+
export type InlineExpression =
181+
StringLiteral |
182+
NumberLiteral |
183+
FunctionReference |
184+
MessageReference |
185+
TermReference |
186+
VariableReference |
187+
Placeable;
188+
export declare type Expression = InlineExpression | SelectExpression;
191189

192190
// An abstract base class for Literals.
193-
export abstract class Literal extends Expression {
194-
public type = "Literal";
191+
export abstract class BaseLiteral extends SyntaxNode {
195192
public value: string;
196193

197194
constructor(value: string) {
@@ -204,7 +201,7 @@ export abstract class Literal extends Expression {
204201
abstract parse(): { value: unknown }
205202
}
206203

207-
export class StringLiteral extends Literal {
204+
export class StringLiteral extends BaseLiteral {
208205
public type = "StringLiteral" as const;
209206

210207
parse(): { value: string } {
@@ -241,7 +238,7 @@ export class StringLiteral extends Literal {
241238
}
242239
}
243240

244-
export class NumberLiteral extends Literal {
241+
export class NumberLiteral extends BaseLiteral {
245242
public type = "NumberLiteral" as const;
246243

247244
parse(): { value: number; precision: number } {
@@ -254,7 +251,9 @@ export class NumberLiteral extends Literal {
254251
}
255252
}
256253

257-
export class MessageReference extends Expression {
254+
export declare type Literal = StringLiteral | NumberLiteral;
255+
256+
export class MessageReference extends SyntaxNode {
258257
public type = "MessageReference" as const;
259258
public id: Identifier;
260259
public attribute: Identifier | null;
@@ -266,7 +265,7 @@ export class MessageReference extends Expression {
266265
}
267266
}
268267

269-
export class TermReference extends Expression {
268+
export class TermReference extends SyntaxNode {
270269
public type = "TermReference" as const;
271270
public id: Identifier;
272271
public attribute: Identifier | null;
@@ -284,7 +283,7 @@ export class TermReference extends Expression {
284283
}
285284
}
286285

287-
export class VariableReference extends Expression {
286+
export class VariableReference extends SyntaxNode {
288287
public type = "VariableReference" as const;
289288
public id: Identifier;
290289

@@ -294,7 +293,7 @@ export class VariableReference extends Expression {
294293
}
295294
}
296295

297-
export class FunctionReference extends Expression {
296+
export class FunctionReference extends SyntaxNode {
298297
public type = "FunctionReference" as const;
299298
public id: Identifier;
300299
public arguments: CallArguments;
@@ -306,12 +305,12 @@ export class FunctionReference extends Expression {
306305
}
307306
}
308307

309-
export class SelectExpression extends Expression {
308+
export class SelectExpression extends SyntaxNode {
310309
public type = "SelectExpression" as const;
311-
public selector: Expression;
310+
public selector: InlineExpression;
312311
public variants: Array<Variant>;
313312

314-
constructor(selector: Expression, variants: Array<Variant>) {
313+
constructor(selector: InlineExpression, variants: Array<Variant>) {
315314
super();
316315
this.selector = selector;
317316
this.variants = variants;
@@ -320,11 +319,11 @@ export class SelectExpression extends Expression {
320319

321320
export class CallArguments extends SyntaxNode {
322321
public type = "CallArguments" as const;
323-
public positional: Array<Expression>;
322+
public positional: Array<InlineExpression>;
324323
public named: Array<NamedArgument>;
325324

326325
constructor(
327-
positional: Array<Expression> = [],
326+
positional: Array<InlineExpression> = [],
328327
named: Array<NamedArgument> = []
329328
) {
330329
super();
@@ -381,8 +380,7 @@ export class Identifier extends SyntaxNode {
381380
}
382381
}
383382

384-
export abstract class BaseComment extends Entry {
385-
public type = "BaseComment";
383+
export abstract class BaseComment extends SyntaxNode {
386384
public content: string;
387385
constructor(content: string) {
388386
super();
@@ -401,6 +399,11 @@ export class ResourceComment extends BaseComment {
401399
public type = "ResourceComment" as const;
402400
}
403401

402+
export declare type Comments =
403+
Comment |
404+
GroupComment |
405+
ResourceComment;
406+
404407
export class Junk extends SyntaxNode {
405408
public type = "Junk" as const;
406409
public annotations: Array<Annotation>;
@@ -418,7 +421,7 @@ export class Junk extends SyntaxNode {
418421
}
419422

420423
export class Span extends BaseNode {
421-
public type = "Span";
424+
public type = "Span" as const;
422425
public start: number;
423426
public end: number;
424427

@@ -430,7 +433,7 @@ export class Span extends BaseNode {
430433
}
431434

432435
export class Annotation extends SyntaxNode {
433-
public type = "Annotation";
436+
public type = "Annotation" as const;
434437
public code: string;
435438
public arguments: Array<unknown>;
436439
public message: string;

fluent-syntax/src/parser.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint no-magic-numbers: [0] */
22

33
import * as AST from "./ast.js";
4+
// eslint-disable-next-line no-duplicate-imports
5+
import type {Resource, Entry} from "./ast.js";
46
import { EOF, EOL, FluentParserStream } from "./stream.js";
57
import { ParseError } from "./errors.js";
68

@@ -12,10 +14,6 @@ type ParseFn<T> =
1214
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1315
(this: FluentParser, ps: FluentParserStream, ...args: Array<any>) => T;
1416

15-
type Comment = AST.Comment | AST.GroupComment | AST.ResourceComment
16-
type Entry = AST.Message | AST.Term | Comment;
17-
type EntryOrJunk = Entry | AST.Junk;
18-
1917
function withSpan<T extends AST.SyntaxNode>(fn: ParseFn<T>): ParseFn<T> {
2018
return function (
2119
this: FluentParser,
@@ -73,11 +71,11 @@ export class FluentParser {
7371
/* eslint-enable @typescript-eslint/unbound-method */
7472
}
7573

76-
parse(source: string): AST.Resource {
74+
parse(source: string): Resource {
7775
const ps = new FluentParserStream(source);
7876
ps.skipBlankBlock();
7977

80-
const entries: Array<EntryOrJunk> = [];
78+
const entries: Array<AST.Entry> = [];
8179
let lastComment: AST.Comment | null = null;
8280

8381
while (ps.currentChar()) {
@@ -133,7 +131,7 @@ export class FluentParser {
133131
* Preceding comments are ignored unless they contain syntax errors
134132
* themselves, in which case Junk for the invalid comment is returned.
135133
*/
136-
parseEntry(source: string): EntryOrJunk {
134+
parseEntry(source: string): Entry {
137135
const ps = new FluentParserStream(source);
138136
ps.skipBlankBlock();
139137

@@ -149,7 +147,7 @@ export class FluentParser {
149147
return this.getEntryOrJunk(ps);
150148
}
151149

152-
getEntryOrJunk(ps: FluentParserStream): EntryOrJunk {
150+
getEntryOrJunk(ps: FluentParserStream): AST.Entry {
153151
const entryStartPos = ps.index;
154152

155153
try {
@@ -182,7 +180,7 @@ export class FluentParser {
182180
}
183181
}
184182

185-
getEntry(ps: FluentParserStream): Entry {
183+
getEntry(ps: FluentParserStream): AST.Entry {
186184
if (ps.currentChar() === "#") {
187185
return this.getComment(ps);
188186
}
@@ -198,7 +196,7 @@ export class FluentParser {
198196
throw new ParseError("E0002");
199197
}
200198

201-
getComment(ps: FluentParserStream): Comment {
199+
getComment(ps: FluentParserStream): AST.Comments {
202200
// 0 - comment
203201
// 1 - group comment
204202
// 2 - resource comment
@@ -669,7 +667,9 @@ export class FluentParser {
669667
return selector;
670668
}
671669

672-
getInlineExpression(ps: FluentParserStream): AST.Expression | AST.Placeable {
670+
getInlineExpression(
671+
ps: FluentParserStream
672+
): AST.InlineExpression | AST.Placeable {
673673
if (ps.currentChar() === "{") {
674674
return this.getPlaceable(ps);
675675
}
@@ -736,7 +736,9 @@ export class FluentParser {
736736
throw new ParseError("E0028");
737737
}
738738

739-
getCallArgument(ps: FluentParserStream): AST.Expression | AST.NamedArgument {
739+
getCallArgument(
740+
ps: FluentParserStream
741+
): AST.InlineExpression | AST.NamedArgument {
740742
const exp = this.getInlineExpression(ps);
741743

742744
ps.skipBlank();
@@ -757,7 +759,7 @@ export class FluentParser {
757759
}
758760

759761
getCallArguments(ps: FluentParserStream): AST.CallArguments {
760-
const positional: Array<AST.Expression> = [];
762+
const positional: Array<AST.InlineExpression> = [];
761763
const named: Array<AST.NamedArgument> = [];
762764
const argumentNames: Set<string> = new Set();
763765

0 commit comments

Comments
 (0)