Skip to content

Commit 55e2d2b

Browse files
committed
Add SyntaxTreeFactory with WasmParser feature flag
Introduce SyntaxTreeFactory to control parser selection (native/wasm) at initialization time via a feature flag, replacing the static ParserType export. - Create SyntaxTreeFactory that resolves parser type once on init - Route all syntax tree creation through the factory - Add WasmParser feature flag (disabled, buildLocalHost) - Emit parser.type metric on each tree creation - Remove static ParserType.ts and init-time parser logging - Wire feature flag from CfnInfraCore during server startup
1 parent 91723f3 commit 55e2d2b

24 files changed

Lines changed: 229 additions & 92 deletions

assets/featureFlag/alpha.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
"FileDb": {
4949
"enabled": true,
5050
"fleetPercentage": 100
51+
},
52+
"WasmParser": {
53+
"enabled": false,
54+
"fleetPercentage": 0
5155
}
5256
}
5357
}

assets/featureFlag/beta.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"FileDb": {
4848
"enabled": false,
4949
"fleetPercentage": 100
50+
},
51+
"WasmParser": {
52+
"enabled": false,
53+
"fleetPercentage": 0
5054
}
5155
}
5256
}

assets/featureFlag/prod.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"FileDb": {
4848
"enabled": false,
4949
"fleetPercentage": 0
50+
},
51+
"WasmParser": {
52+
"enabled": false,
53+
"fleetPercentage": 0
5054
}
5155
}
5256
}

src/app/initialize.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { arch, machine, platform, release, type } from 'os';
2-
import { parserType } from '../parser/ParserType';
32
import { AwsMetadata, ClientInfo } from '../server/InitParams';
43
import { LoggerFactory } from '../telemetry/LoggerFactory';
54
import { TelemetryService } from '../telemetry/TelemetryService';
@@ -18,7 +17,6 @@ export function staticInitialize(ClientInfo?: ClientInfo, AwsMetadata?: AwsMetad
1817
Process: ProcessType,
1918
Machine: `${type()}-${platform()}-${arch()}-${machine()}-${release()}`,
2019
Runtime: `node=${process.versions.node} v8=${process.versions.v8} uv=${process.versions.uv} modules=${process.versions.modules}`,
21-
Parser: parserType,
2220
ClientInfo,
2321
aws: {
2422
clientInfo: AwsMetadata?.clientInfo,

src/app/standalone.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ async function onInitialize(params: ExtendedInitializeParams) {
1414
staticInitialize(params.clientInfo, params.initializationOptions?.['aws']);
1515

1616
// Dynamically load these modules so that OTEL can instrument all the libraries first
17-
const { parserFactoryReady } = await import('../parser/ParserFactory');
18-
await parserFactoryReady;
17+
const { syntaxTreeFactory } = await import('../context/syntaxtree/SyntaxTreeFactory');
18+
await syntaxTreeFactory.ready;
1919

2020
const { CfnInfraCore } = await import('../server/CfnInfraCore');
2121
const core = new CfnInfraCore(lsp.components, params);
2222

23+
// CfnInfraCore.initialize may switch to WASM based on feature flag
24+
await syntaxTreeFactory.ready;
25+
2326
const { CfnServer } = await import('../server/CfnServer');
2427
server = new CfnServer(lsp.components, core);
2528
return LspCapabilities;
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { DocumentType } from '../../document/Document';
2+
import { ParserFactory } from '../../parser/ParserFactory';
23
import { SyntaxTree } from './SyntaxTree';
4+
import { ParserType } from './SyntaxTreeFactory';
35

46
export class JsonSyntaxTree extends SyntaxTree {
5-
constructor(content: string) {
6-
super(DocumentType.JSON, content);
7+
constructor(content: string, factory: ParserFactory, parserType: ParserType) {
8+
super(DocumentType.JSON, content, factory, parserType);
79
}
810
}

src/context/syntaxtree/SyntaxTree.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Edit, Point, SyntaxNode, Tree } from 'tree-sitter';
22
import { Position } from 'vscode-languageserver-textdocument';
33
import { DocumentType } from '../../document/Document';
44
import { createEdit } from '../../document/DocumentUtils';
5-
import { parserFactory } from '../../parser/ParserFactory';
6-
import { parserType } from '../../parser/ParserType';
5+
import { ParserFactory } from '../../parser/ParserFactory';
76
import { Measure } from '../../telemetry/TelemetryDecorator';
87
import { TopLevelSection, TopLevelSections, IntrinsicsSet } from '../CloudFormationEnums';
98
import { normalizeIntrinsicFunction } from '../semantic/Intrinsics';
9+
import { ParserType } from './SyntaxTreeFactory';
1010
import { extractEntityFromNodeTextYaml } from './utils/NodeParse';
1111
import { NodeSearch } from './utils/NodeSearch';
1212
import { NodeStructure } from './utils/NodeStructure';
@@ -29,15 +29,19 @@ export abstract class SyntaxTree {
2929
private readonly parser;
3030
private rawContent: string;
3131
private _lines: string[] | undefined;
32+
private readonly parserType: ParserType;
3233

3334
protected constructor(
3435
public readonly type: DocumentType,
3536
content: string,
37+
factory: ParserFactory,
38+
parserType: ParserType,
3639
) {
40+
this.parserType = parserType;
3741
if (type === DocumentType.YAML) {
38-
this.parser = parserFactory.createYamlParser();
42+
this.parser = factory.createYamlParser();
3943
} else {
40-
this.parser = parserFactory.createJsonParser();
44+
this.parser = factory.createJsonParser();
4145
}
4246
this.rawContent = content;
4347
this.tree = this.parser.parse(this.rawContent);
@@ -48,21 +52,21 @@ export abstract class SyntaxTree {
4852
return this._lines;
4953
}
5054

51-
@Measure({ name: 'updateWithEdit', captureErrorAttributes: true, attributes: { 'parser.type': parserType } })
55+
@Measure({ name: 'updateWithEdit', captureErrorAttributes: true })
5256
public updateWithEdit(content: string, edit: Edit) {
5357
this._lines = undefined; // Invalidate cache
5458
this.rawContent = content; // Update raw content
5559
this.tree.edit(edit);
5660
this.tree = this.parser.parse(content, this.tree);
5761
}
5862

59-
@Measure({ name: 'update', captureErrorAttributes: true, attributes: { 'parser.type': parserType } })
63+
@Measure({ name: 'update', captureErrorAttributes: true })
6064
public update(textToInsert: string, start: Point, end: Point) {
6165
const { newContent, edit } = createEdit(this.content(), textToInsert, start, end);
6266
this.updateWithEdit(newContent, edit);
6367
}
6468

65-
@Measure({ name: 'getNodeAtPosition', captureErrorAttributes: true, attributes: { 'parser.type': parserType } })
69+
@Measure({ name: 'getNodeAtPosition', captureErrorAttributes: true })
6670
public getNodeAtPosition(position: Position): SyntaxNode {
6771
const point: Point = {
6872
row: position.line,
@@ -416,7 +420,7 @@ export abstract class SyntaxTree {
416420
* Analyzes a node to determine its semantic path within the document.
417421
* It walks up the tree from the given node, building a property path and identifying the entity root.
418422
*/
419-
@Measure({ name: 'getPathAndEntityInfo', captureErrorAttributes: true, attributes: { 'parser.type': parserType } })
423+
@Measure({ name: 'getPathAndEntityInfo', captureErrorAttributes: true })
420424
public getPathAndEntityInfo(node: SyntaxNode): PathAndEntity {
421425
if (!node) {
422426
return {
@@ -842,7 +846,7 @@ export abstract class SyntaxTree {
842846
* @param pathSegments Array like ["Resources", "MyBucket", "Properties", "BucketName"] or ["Resources", "MyBucket", "Properties", 0]
843847
* @returns Object with the node and whether the full path was resolved
844848
*/
845-
@Measure({ name: 'getNodeByPath', captureErrorAttributes: true, attributes: { 'parser.type': parserType } })
849+
@Measure({ name: 'getNodeByPath', captureErrorAttributes: true })
846850
getNodeByPath(pathSegments: ReadonlyArray<string | number>): {
847851
node: SyntaxNode | undefined;
848852
fullyResolved: boolean;
@@ -962,7 +966,7 @@ export abstract class SyntaxTree {
962966
}
963967

964968
// Finds CloudFormation sections (Parameters, Resources, etc.)
965-
@Measure({ name: 'findTopLevelSections', attributes: { 'parser.type': parserType } })
969+
@Measure({ name: 'findTopLevelSections' })
966970
public findTopLevelSections(sectionsToFind: TopLevelSection[]): Map<TopLevelSection, SyntaxNode> {
967971
const result = new Map<TopLevelSection, SyntaxNode>();
968972
if (sectionsToFind.length === 0) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { DocumentType } from '../../document/Document';
2+
import { FeatureFlag } from '../../featureFlag/FeatureFlagI';
3+
import { ParserFactory, parserFactory, parserFactoryReady } from '../../parser/ParserFactory';
4+
import { WasmParserFactory } from '../../parser/WasmParserFactory';
5+
import { LoggerFactory } from '../../telemetry/LoggerFactory';
6+
import { ScopedTelemetry } from '../../telemetry/ScopedTelemetry';
7+
import { Telemetry } from '../../telemetry/TelemetryDecorator';
8+
import { JsonSyntaxTree } from './JsonSyntaxTree';
9+
import { SyntaxTree } from './SyntaxTree';
10+
import { YamlSyntaxTree } from './YamlSyntaxTree';
11+
12+
export type ParserType = 'native' | 'wasm';
13+
14+
const log = LoggerFactory.getLogger('SyntaxTreeFactory');
15+
const isLegacyLinux = process.env.BUILD_TARGET === 'legacy';
16+
17+
export class SyntaxTreeFactory {
18+
@Telemetry() private readonly telemetry!: ScopedTelemetry;
19+
20+
private factory: ParserFactory;
21+
private type: ParserType;
22+
private readyPromise: Promise<void>;
23+
24+
constructor(nativeFactory: ParserFactory = parserFactory) {
25+
this.factory = nativeFactory;
26+
this.type = isLegacyLinux ? 'wasm' : 'native';
27+
this.readyPromise = parserFactoryReady;
28+
}
29+
30+
/**
31+
* Called once during server initialization to lock in the parser type
32+
* for the lifetime of the session based on the feature flag state.
33+
*/
34+
initialize(wasmFlag: FeatureFlag): void {
35+
if (isLegacyLinux) {
36+
return; // Already using WASM via parserFactory
37+
}
38+
if (wasmFlag.isEnabled()) {
39+
log.info('WasmParser feature flag enabled, switching to WASM parser');
40+
const wasm = new WasmParserFactory();
41+
this.factory = wasm;
42+
this.type = 'wasm';
43+
this.readyPromise = wasm.initialize().catch((error: unknown) => {
44+
log.error(error, 'WASM initialization failed, falling back to native');
45+
this.factory = parserFactory;
46+
this.type = 'native';
47+
});
48+
}
49+
}
50+
51+
get parserType(): ParserType {
52+
return this.type;
53+
}
54+
55+
get ready(): Promise<void> {
56+
return this.readyPromise;
57+
}
58+
59+
createSyntaxTree(content: string, documentType: DocumentType): SyntaxTree {
60+
this.telemetry.count('createSyntaxTree', 1, {
61+
attributes: { 'parser.type': this.type },
62+
});
63+
if (documentType === DocumentType.JSON) {
64+
return new JsonSyntaxTree(content, this.factory, this.type);
65+
}
66+
return new YamlSyntaxTree(content, this.factory, this.type);
67+
}
68+
}
69+
70+
export const syntaxTreeFactory = new SyntaxTreeFactory();

src/context/syntaxtree/SyntaxTreeManager.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { CloudFormationFileType, DocumentType } from '../../document/Document';
33
import { detectDocumentType } from '../../document/DocumentUtils';
44
import { LoggerFactory } from '../../telemetry/LoggerFactory';
55
import { Measure } from '../../telemetry/TelemetryDecorator';
6-
import { JsonSyntaxTree } from './JsonSyntaxTree';
76
import { SyntaxTree } from './SyntaxTree';
8-
import { YamlSyntaxTree } from './YamlSyntaxTree';
7+
import { syntaxTreeFactory } from './SyntaxTreeFactory';
98

109
const logger = LoggerFactory.getLogger('SyntaxTreeManager');
1110

@@ -50,11 +49,11 @@ export class SyntaxTreeManager {
5049
}
5150

5251
private createJsonSyntaxTree(uri: string, content: string) {
53-
this.syntaxTrees.set(uri, new JsonSyntaxTree(content));
52+
this.syntaxTrees.set(uri, syntaxTreeFactory.createSyntaxTree(content, DocumentType.JSON));
5453
}
5554

5655
private createYamlSyntaxTree(uri: string, content: string) {
57-
this.syntaxTrees.set(uri, new YamlSyntaxTree(content));
56+
this.syntaxTrees.set(uri, syntaxTreeFactory.createSyntaxTree(content, DocumentType.YAML));
5857
}
5958

6059
public getSyntaxTree(uri: string): SyntaxTree | undefined {
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { DocumentType } from '../../document/Document';
2+
import { ParserFactory } from '../../parser/ParserFactory';
23
import { SyntaxTree } from './SyntaxTree';
4+
import { ParserType } from './SyntaxTreeFactory';
35

46
export class YamlSyntaxTree extends SyntaxTree {
5-
constructor(content: string) {
6-
super(DocumentType.YAML, content);
7+
constructor(content: string, factory: ParserFactory, parserType: ParserType) {
8+
super(DocumentType.YAML, content, factory, parserType);
79
}
810
}

0 commit comments

Comments
 (0)