diff --git a/lana/package.json b/lana/package.json index cb4920bb..91f2eec6 100644 --- a/lana/package.json +++ b/lana/package.json @@ -315,7 +315,7 @@ }, "dependencies": { "@apexdevtools/apex-ls": "^6.0.2", - "@apexdevtools/apex-parser": "^4.4.0", + "@apexdevtools/apex-parser": "5.0.0-beta.5", "@salesforce/apex-node": "^8.4.11", "@salesforce/core": "^8.26.3" }, diff --git a/lana/src/salesforce/ApexParser/ApexSymbolLocator.ts b/lana/src/salesforce/ApexParser/ApexSymbolLocator.ts index 9b6c1aec..d70c6a17 100644 --- a/lana/src/salesforce/ApexParser/ApexSymbolLocator.ts +++ b/lana/src/salesforce/ApexParser/ApexSymbolLocator.ts @@ -1,13 +1,7 @@ /* * Copyright (c) 2025 Certinia Inc. All rights reserved. */ -import { - ApexLexer, - ApexParser, - CaseInsensitiveInputStream, - CommonTokenStream, -} from '@apexdevtools/apex-parser'; -import { CharStreams } from 'antlr4ts'; +import { ApexParserFactory } from '@apexdevtools/apex-parser'; import { ApexVisitor, type ApexConstructorNode, @@ -23,11 +17,7 @@ export type SymbolLocation = { }; export function parseApex(apexCode: string): ApexNode { - const parser = new ApexParser( - new CommonTokenStream( - new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(apexCode))), - ), - ); + const parser = ApexParserFactory.createParser(apexCode); return new ApexVisitor().visit(parser.compilationUnit()); } diff --git a/lana/src/salesforce/ApexParser/ApexVisitor.ts b/lana/src/salesforce/ApexParser/ApexVisitor.ts index 0146fc5e..4b76f337 100644 --- a/lana/src/salesforce/ApexParser/ApexVisitor.ts +++ b/lana/src/salesforce/ApexParser/ApexVisitor.ts @@ -1,14 +1,17 @@ /* * Copyright (c) 2025 Certinia Inc. All rights reserved. */ -import type { - ApexParserVisitor, - ClassDeclarationContext, - ConstructorDeclarationContext, - FormalParametersContext, - MethodDeclarationContext, +import { + ApexParserBaseVisitor, + type ApexErrorNode, + type ApexParserRuleContext, + type ApexParseTree, + type ApexTerminalNode, + type ClassDeclarationContext, + type ConstructorDeclarationContext, + type FormalParametersContext, + type MethodDeclarationContext, } from '@apexdevtools/apex-parser'; -import type { ErrorNode, ParseTree, RuleNode, TerminalNode } from 'antlr4ts/tree'; type ApexNature = 'Constructor' | 'Class' | 'Method'; @@ -67,19 +70,18 @@ export interface ApexConstructorNode extends ApexParamNode { nature: 'Constructor'; } -type VisitableApex = ParseTree & { - accept(visitor: ApexParserVisitor): Result; -}; - -export class ApexVisitor implements ApexParserVisitor { - visit(ctx: ParseTree): ApexNode { - return ctx ? (ctx as VisitableApex).accept(this) : {}; +export class ApexVisitor extends ApexParserBaseVisitor { + override visit(ctx: ApexParseTree): ApexNode { + if (!ctx) { + return {}; + } + return super.visit(ctx); } - visitChildren(ctx: RuleNode): ApexNode { + override visitChildren(ctx: ApexParserRuleContext): ApexNode { const children: ApexNode[] = []; - for (let index = 0; index < ctx.childCount; index++) { + for (let index = 0; index < ctx.getChildCount(); index++) { const child = ctx.getChild(index); const node = this.visit(child); if (!node) { @@ -98,25 +100,25 @@ export class ApexVisitor implements ApexParserVisitor { return { nature: 'Class', - name: ident.text ?? '', + name: ident.getText() ?? '', children: ctx.children?.length ? this.visitChildren(ctx).children : [], line: start.line, - idCharacter: ident.start.charPositionInLine ?? 0, + idCharacter: ident.start.column ?? 0, }; } visitConstructorDeclaration(ctx: ConstructorDeclarationContext): ApexConstructorNode { const { start } = ctx; - const idContexts = ctx.qualifiedName().id(); + const idContexts = ctx.qualifiedName().id_list(); const constructorName = idContexts[idContexts.length - 1]; return { nature: 'Constructor', - name: constructorName?.text ?? '', + name: constructorName?.getText() ?? '', children: ctx.children?.length ? this.visitChildren(ctx).children : [], params: this.getParameters(ctx.formalParameters()), line: start.line, - idCharacter: start.charPositionInLine ?? 0, + idCharacter: start.column ?? 0, }; } @@ -126,25 +128,25 @@ export class ApexVisitor implements ApexParserVisitor { return { nature: 'Method', - name: ident.text ?? '', + name: ident.getText() ?? '', children: ctx.children?.length ? this.visitChildren(ctx).children : [], params: this.getParameters(ctx.formalParameters()), line: start.line, - idCharacter: ident.start.charPositionInLine ?? 0, + idCharacter: ident.start.column ?? 0, }; } - visitTerminal(_ctx: TerminalNode): ApexNode { + override visitTerminal(_ctx: ApexTerminalNode): ApexNode { return {}; } - visitErrorNode(_ctx: ErrorNode): ApexNode { + override visitErrorNode(_ctx: ApexErrorNode): ApexNode { return {}; } private getParameters(ctx: FormalParametersContext): string { - const paramsList = ctx.formalParameterList()?.formalParameter(); - return paramsList?.map((param) => param.typeRef().text).join(',') ?? ''; + const paramsList = ctx.formalParameterList()?.formalParameter_list(); + return paramsList?.map((param) => param.typeRef().getText()).join(',') ?? ''; } private forNode(node: ApexNode, anonHandler: (n: ApexNode) => void) { diff --git a/lana/src/salesforce/__tests__/ApexSymbolLocator.test.ts b/lana/src/salesforce/__tests__/ApexSymbolLocator.test.ts index ef12a519..07dfe8dd 100644 --- a/lana/src/salesforce/__tests__/ApexSymbolLocator.test.ts +++ b/lana/src/salesforce/__tests__/ApexSymbolLocator.test.ts @@ -5,7 +5,12 @@ import { getMethodLine, parseApex } from '../ApexParser/ApexSymbolLocator'; import { ApexVisitor, type ApexNode } from '../ApexParser/ApexVisitor'; jest.mock('../ApexParser/ApexVisitor'); -jest.mock('@apexdevtools/apex-parser'); +jest.mock('@apexdevtools/apex-parser', () => ({ + ApexParserFactory: { + createParser: () => ({ compilationUnit: () => ({}) }), + }, + ApexParserBaseVisitor: class {}, +})); describe('ApexSymbolLocator', () => { const mockAST = { diff --git a/lana/src/salesforce/__tests__/ApexVisitor.test.ts b/lana/src/salesforce/__tests__/ApexVisitor.test.ts index 8397c960..736d02ad 100644 --- a/lana/src/salesforce/__tests__/ApexVisitor.test.ts +++ b/lana/src/salesforce/__tests__/ApexVisitor.test.ts @@ -5,9 +5,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { ApexVisitor } from '../ApexParser/ApexVisitor'; -jest.mock('@apexdevtools/apex-parser'); -jest.mock('antlr4ts/tree'); - describe('ApexVisitor', () => { let visitor: ApexVisitor; @@ -16,11 +13,11 @@ describe('ApexVisitor', () => { }); describe('visitClassDeclaration', () => { - it('should use empty string when ident.text is null', () => { + it('should use empty string when ident getText returns null', () => { const ctx = { id: () => ({ - text: null, - start: { charPositionInLine: 5 }, + getText: () => null, + start: { column: 5 }, }), children: [], start: { line: 1 }, @@ -32,11 +29,11 @@ describe('ApexVisitor', () => { expect(node.name).toBe(''); }); - it('should use 0 when charPositionInLine is null', () => { + it('should use 0 when column is null', () => { const ctx = { id: () => ({ - text: 'MyClass', - start: { charPositionInLine: null }, + getText: () => 'MyClass', + start: { column: null }, }), children: [], start: { line: 1 }, @@ -51,13 +48,11 @@ describe('ApexVisitor', () => { it('should return class node with name and children', () => { const ctx = { id: () => ({ - text: 'MyClass', - start: { charPositionInLine: 0 }, + getText: () => 'MyClass', + start: { column: 0 }, }), children: [{}], - get childCount() { - return 1; - }, + getChildCount: () => 1, getChild: jest.fn().mockReturnValue({ accept: jest.fn().mockReturnValue({ nature: 'Method', name: 'foo' }), }), @@ -78,8 +73,8 @@ describe('ApexVisitor', () => { it('should handle missing Identifier', () => { const ctx = { id: () => ({ - text: '', - start: { charPositionInLine: 0 }, + getText: () => '', + start: { column: 0 }, }), children: [], start: { line: 10 }, @@ -95,8 +90,8 @@ describe('ApexVisitor', () => { it('should handle missing children', () => { const ctx = { id: () => ({ - text: 'NoChildren', - start: { charPositionInLine: 0 }, + getText: () => 'NoChildren', + start: { column: 0 }, }), children: undefined, start: { line: 15 }, @@ -110,11 +105,11 @@ describe('ApexVisitor', () => { }); describe('visitMethodDeclaration', () => { - it('should use empty string when ident.text is null', () => { + it('should use empty string when ident getText returns null', () => { const ctx = { id: () => ({ - text: null, - start: { charPositionInLine: 5 }, + getText: () => null, + start: { column: 5 }, }), children: [], formalParameters: () => ({ @@ -129,11 +124,11 @@ describe('ApexVisitor', () => { expect(node.name).toBe(''); }); - it('should use 0 when charPositionInLine is null', () => { + it('should use 0 when column is null', () => { const ctx = { id: () => ({ - text: 'myMethod', - start: { charPositionInLine: null }, + getText: () => 'myMethod', + start: { column: null }, }), children: [], formalParameters: () => ({ @@ -151,15 +146,15 @@ describe('ApexVisitor', () => { it('should return method node with name, params, and line', () => { const ctx = { id: () => ({ - text: 'myMethod', - start: { charPositionInLine: 2 }, + getText: () => 'myMethod', + start: { column: 2 }, }), children: [{}], formalParameters: () => ({ formalParameterList: () => ({ - formalParameter: () => [ - { typeRef: () => ({ text: 'Integer' }) }, - { typeRef: () => ({ text: 'String' }) }, + formalParameter_list: () => [ + { typeRef: () => ({ getText: () => 'Integer' }) }, + { typeRef: () => ({ getText: () => 'String' }) }, ], }), }), @@ -178,8 +173,8 @@ describe('ApexVisitor', () => { it('should handle missing Identifier and params', () => { const ctx = { id: () => ({ - text: '', - start: { charPositionInLine: 0 }, + getText: () => '', + start: { column: 0 }, }), children: [], formalParameters: () => ({ @@ -197,16 +192,16 @@ describe('ApexVisitor', () => { }); describe('visitConstructorDeclaration', () => { - it('should use empty string when constructorName.text is null', () => { + it('should use empty string when constructorName getText returns null', () => { const ctx = { qualifiedName: () => ({ - id: () => [{ text: null }], + id_list: () => [{ getText: () => null }], }), children: [], formalParameters: () => ({ formalParameterList: () => undefined, }), - start: { line: 1, charPositionInLine: 5 }, + start: { line: 1, column: 5 }, }; visitor.visitChildren = jest.fn().mockReturnValue({ children: [] }); @@ -215,16 +210,16 @@ describe('ApexVisitor', () => { expect(node.name).toBe(''); }); - it('should use 0 when start.charPositionInLine is null', () => { + it('should use 0 when start.column is null', () => { const ctx = { qualifiedName: () => ({ - id: () => [{ text: 'MyConstructor' }], + id_list: () => [{ getText: () => 'MyConstructor' }], }), children: [], formalParameters: () => ({ formalParameterList: () => undefined, }), - start: { line: 1, charPositionInLine: null }, + start: { line: 1, column: null }, }; visitor.visitChildren = jest.fn().mockReturnValue({ children: [] }); @@ -236,18 +231,18 @@ describe('ApexVisitor', () => { it('should return constructor node with name, params, and line', () => { const ctx = { qualifiedName: () => ({ - id: () => [{ text: 'OuterClass' }, { text: 'MyConstructor' }], + id_list: () => [{ getText: () => 'OuterClass' }, { getText: () => 'MyConstructor' }], }), children: [{}], formalParameters: () => ({ formalParameterList: () => ({ - formalParameter: () => [ - { typeRef: () => ({ text: 'String' }) }, - { typeRef: () => ({ text: 'Integer' }) }, + formalParameter_list: () => [ + { typeRef: () => ({ getText: () => 'String' }) }, + { typeRef: () => ({ getText: () => 'Integer' }) }, ], }), }), - start: { line: 20, charPositionInLine: 5 }, + start: { line: 20, column: 5 }, }; visitor.visitChildren = jest.fn().mockReturnValue({ children: [] }); @@ -263,13 +258,13 @@ describe('ApexVisitor', () => { it('should handle constructor with no params', () => { const ctx = { qualifiedName: () => ({ - id: () => [{ text: 'MyClass' }], + id_list: () => [{ getText: () => 'MyClass' }], }), children: [], formalParameters: () => ({ formalParameterList: () => undefined, }), - start: { line: 10, charPositionInLine: 2 }, + start: { line: 10, column: 2 }, }; visitor.visitChildren = jest.fn().mockReturnValue({ children: [] }); @@ -284,15 +279,19 @@ describe('ApexVisitor', () => { it('should handle nested class constructor', () => { const ctx = { qualifiedName: () => ({ - id: () => [{ text: 'OuterClass' }, { text: 'InnerClass' }, { text: 'InnerClass' }], + id_list: () => [ + { getText: () => 'OuterClass' }, + { getText: () => 'InnerClass' }, + { getText: () => 'InnerClass' }, + ], }), children: [{}], formalParameters: () => ({ formalParameterList: () => ({ - formalParameter: () => [{ typeRef: () => ({ text: 'Boolean' }) }], + formalParameter_list: () => [{ typeRef: () => ({ getText: () => 'Boolean' }) }], }), }), - start: { line: 35, charPositionInLine: 10 }, + start: { line: 35, column: 10 }, }; visitor.visitChildren = jest.fn().mockReturnValue({ children: [] }); @@ -340,7 +339,7 @@ describe('ApexVisitor', () => { describe('visitChildren', () => { it('should skip null nodes returned from visit', () => { const ctx = { - childCount: 2, + getChildCount: () => 2, getChild: jest.fn().mockImplementation((index: number) => ({ accept: jest.fn().mockReturnValue(index === 0 ? null : { nature: 'Method', name: 'foo' }), })), @@ -354,7 +353,7 @@ describe('ApexVisitor', () => { it('should skip undefined nodes returned from visit', () => { const ctx = { - childCount: 2, + getChildCount: () => 2, getChild: jest.fn().mockImplementation((index: number) => ({ accept: jest .fn() @@ -370,7 +369,7 @@ describe('ApexVisitor', () => { it('should process multiple valid nodes', () => { const ctx = { - childCount: 3, + getChildCount: () => 3, getChild: jest.fn().mockImplementation((index: number) => ({ accept: jest.fn().mockReturnValue({ nature: 'Method', name: `method${index}` }), })), @@ -384,7 +383,7 @@ describe('ApexVisitor', () => { it('should flatten children from non-anon nodes (nodes without nature)', () => { // A node without 'nature' should have its children extracted const ctx = { - childCount: 1, + getChildCount: () => 1, getChild: jest.fn().mockReturnValue({ accept: jest.fn().mockReturnValue({ // No nature property - this is a "non-anon" wrapper node @@ -406,7 +405,7 @@ describe('ApexVisitor', () => { it('should handle non-anon nodes with empty children array', () => { const ctx = { - childCount: 1, + getChildCount: () => 1, getChild: jest.fn().mockReturnValue({ accept: jest.fn().mockReturnValue({ // No nature, empty children @@ -422,7 +421,7 @@ describe('ApexVisitor', () => { it('should handle non-anon nodes with no children property', () => { const ctx = { - childCount: 1, + getChildCount: () => 1, getChild: jest.fn().mockReturnValue({ accept: jest.fn().mockReturnValue({ // No nature, no children property @@ -439,7 +438,7 @@ describe('ApexVisitor', () => { it('should handle mix of anon and non-anon nodes', () => { const ctx = { - childCount: 2, + getChildCount: () => 2, getChild: jest.fn().mockImplementation((index: number) => ({ accept: jest.fn().mockReturnValue( index === 0 diff --git a/log-viewer/package.json b/log-viewer/package.json index a7e0db93..bbc25412 100644 --- a/log-viewer/package.json +++ b/log-viewer/package.json @@ -5,7 +5,7 @@ "scripts": {}, "version": "0.1.0", "dependencies": { - "@apexdevtools/apex-parser": "^4.4.0", + "@apexdevtools/apex-parser": "5.0.0-beta.5", "@vscode/codicons": "^0.0.44", "@vscode/webview-ui-toolkit": "^1.4.0", "lit": "^3.3.2", diff --git a/log-viewer/src/__tests__/SOQLParser.test.ts b/log-viewer/src/__tests__/SOQLParser.test.ts index 31d1dea2..2453e2dd 100644 --- a/log-viewer/src/__tests__/SOQLParser.test.ts +++ b/log-viewer/src/__tests__/SOQLParser.test.ts @@ -1,7 +1,9 @@ /* * Copyright (c) 2020 Certinia Inc. All rights reserved. */ -import { SOQLParser, SyntaxException } from '../features/soql/services/SOQLParser.js'; +import { ApexSyntaxError } from '@apexdevtools/apex-parser'; + +import { SOQLParser } from '../features/soql/services/SOQLParser.js'; describe('Analyse database tests', () => { it('throws on unparsable query', async () => { @@ -10,7 +12,10 @@ describe('Analyse database tests', () => { await parser.parse(''); expect(true).toBe(false); } catch (ex) { - expect(ex).toEqual(new SyntaxException(1, 0, "mismatched input '' expecting 'select'")); + expect(ex).toBeInstanceOf(ApexSyntaxError); + const syntaxError = ex as ApexSyntaxError; + expect(syntaxError.line).toBe(1); + expect(syntaxError.column).toBe(0); } }); diff --git a/log-viewer/src/features/soql/services/SOQLLinter.ts b/log-viewer/src/features/soql/services/SOQLLinter.ts index c4fc6260..a2a0535e 100644 --- a/log-viewer/src/features/soql/services/SOQLLinter.ts +++ b/log-viewer/src/features/soql/services/SOQLLinter.ts @@ -66,12 +66,12 @@ class LeadingPercentWildcardRule implements SOQLLinterRule { if (whereClause) { const hasLeadingWildcard = whereClause .logicalExpression() - .conditionalExpression() + .conditionalExpression_list() .find((exp) => { const fieldExp = exp.fieldExpression(); if ( fieldExp?.comparisonOperator().LIKE() && - fieldExp.value().StringLiteral()?.text.startsWith("'%") + fieldExp.value().StringLiteral()?.getText().startsWith("'%") ) { return exp; } @@ -99,7 +99,7 @@ class NegativeFilterOperatorRule implements SOQLLinterRule { const hasNegativeOp = exp.NOT() || - exp.conditionalExpression().find((exp) => { + exp.conditionalExpression_list().find((exp) => { const operator = exp.fieldExpression()?.comparisonOperator(); if ( @@ -148,11 +148,11 @@ class LastModifiedDateSystemModStampIndexRule implements SOQLLinterRule { if (whereClause) { const result = whereClause .logicalExpression() - .conditionalExpression() + .conditionalExpression_list() .find((exp) => { const fieldExp = exp.fieldExpression(); if ( - fieldExp?.fieldName()?.text.toLowerCase().endsWith('lastmodifieddate') && + fieldExp?.fieldName()?.getText().toLowerCase().endsWith('lastmodifieddate') && fieldExp.comparisonOperator().LT() ) { return exp; diff --git a/log-viewer/src/features/soql/services/SOQLParser.ts b/log-viewer/src/features/soql/services/SOQLParser.ts index 5bc44f7b..f897c8a7 100644 --- a/log-viewer/src/features/soql/services/SOQLParser.ts +++ b/log-viewer/src/features/soql/services/SOQLParser.ts @@ -2,12 +2,6 @@ * Copyright (c) 2022 Certinia Inc. All rights reserved. */ import { type QueryContext } from '@apexdevtools/apex-parser'; -import { - type ANTLRErrorListener, - type RecognitionException, - type Recognizer, - type Token, -} from 'antlr4ts'; // To understand the parser AST see https://github.com/nawforce/apex-parser/blob/master/antlr/ApexParser.g4 // Start with the 'query' rule at ~532 @@ -23,46 +17,46 @@ export class SOQLTree { /* Return true if SELECT list only contains field names, no functions, sub-queries or typeof */ isSimpleSelect(): boolean { const selectList = this._queryContext.selectList(); - const selectEntries = selectList.selectEntry(); - return selectEntries.every((selectEntry) => selectEntry.fieldName() !== undefined); + const selectEntries = selectList.selectEntry_list(); + return selectEntries.every((selectEntry) => selectEntry.fieldName() != null); } /* Return true for queries only containing WHERE, ORDER BY & LIMIT clauses */ isTrivialQuery(): boolean { return ( - this._queryContext.usingScope() === undefined && - this._queryContext.withClause() === undefined && - this._queryContext.groupByClause() === undefined && - this._queryContext.offsetClause() === undefined && - this._queryContext.allRowsClause() === undefined && - this._queryContext.forClauses().childCount === 0 && - this._queryContext.updateList() === undefined + this._queryContext.usingScope() == null && + this._queryContext.withClause() == null && + this._queryContext.groupByClause() == null && + this._queryContext.offsetClause() == null && + this._queryContext.allRowsClause() == null && + this._queryContext.forClauses().getChildCount() === 0 && + this._queryContext.updateList() == null ); } /* Return true if query has ORDER BY */ isOrdered(): boolean { - return this._queryContext.orderByClause() !== undefined; + return this._queryContext.orderByClause() != null; } /* Return limit value if defined, maybe a number or a bound expression */ limitValue(): number | string | undefined { const limitClause = this._queryContext.limitClause(); - if (limitClause === undefined) { + if (limitClause == null) { return undefined; - } else if (limitClause?.IntegerLiteral() !== undefined) { - return parseInt(limitClause?.IntegerLiteral()?.text as string); + } else if (limitClause.IntegerLiteral() != null) { + return parseInt(limitClause.IntegerLiteral()!.getText() as string); } else { - return limitClause?.boundExpression()?.text as string; + return limitClause.boundExpression()?.getText() as string; } } /* Return FROM clase SObject name, if there is a single SObject */ fromObject(): undefined | string { const fromContext = this._queryContext.fromNameList(); - const fieldNames = fromContext.fieldName(); + const fieldNames = fromContext.fieldName_list(); if (fieldNames.length === 1) { - return fieldNames[0]?.text; + return fieldNames[0]?.getText(); } else { return undefined; } @@ -73,42 +67,10 @@ export class SOQLParser { async parse(query: string): Promise { // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. // eslint-disable-next-line @typescript-eslint/naming-convention - const { ApexLexer, ApexParser, CaseInsensitiveInputStream } = - await import('@apexdevtools/apex-parser'); - // Dynamic import for code splitting. Improves performance by reducing the amount of JS that is loaded and parsed at the start. - // eslint-disable-next-line @typescript-eslint/naming-convention - const { CharStreams, CommonTokenStream } = await import('antlr4ts'); - const lexer = new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(query))); - const tokens = new CommonTokenStream(lexer); - const parser = new ApexParser(tokens); + const { ApexParserFactory, ThrowingErrorListener } = await import('@apexdevtools/apex-parser'); + const parser = ApexParserFactory.createParser(query, false); parser.removeErrorListeners(); - parser.addErrorListener(new ThrowingErrorListener()); + parser.addErrorListener(ThrowingErrorListener.INSTANCE); return new SOQLTree(parser.query()); } } - -export class SyntaxException { - line: number; - column: number; - message: string; - - constructor(line: number, column: number, message: string) { - this.line = line; - this.column = column; - this.message = message; - } -} - -class ThrowingErrorListener implements ANTLRErrorListener { - syntaxError( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - recognizer: Recognizer, - offendingSymbol: Token, - line: number, - charPositionInLine: number, - msg: string, - _e: RecognitionException | undefined, - ): void { - throw new SyntaxException(line, charPositionInLine, msg); - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1e58958..70206124 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,8 +99,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 '@apexdevtools/apex-parser': - specifier: ^4.4.0 - version: 4.4.1 + specifier: 5.0.0-beta.5 + version: 5.0.0-beta.5 '@salesforce/apex-node': specifier: ^8.4.11 version: 8.4.11(tslib@2.8.1) @@ -164,8 +164,8 @@ importers: log-viewer: dependencies: '@apexdevtools/apex-parser': - specifier: ^4.4.0 - version: 4.4.1 + specifier: 5.0.0-beta.5 + version: 5.0.0-beta.5 '@vscode/codicons': specifier: ^0.0.44 version: 0.0.44 @@ -278,9 +278,11 @@ packages: resolution: {integrity: sha512-RlaWpAFudE0GvUcim/xLBNw5ipNgT0Yd1eM7jeZJS0R9qn5DxeUGY3636zfgKgWkK04075y1fgNbPsIi/EjKiw==} engines: {node: '>=8.0.0'} - '@apexdevtools/apex-parser@4.4.1': - resolution: {integrity: sha512-tLHQ8DkI7/aoL9nOax+Xb3OEXk8IK1mTIpcCBaBJ3kk0Mhy4ik9jfQVAoSxjbWo8aLrjz2E4jnjmSU1iZlEt+Q==} - engines: {node: '>=8.0.0'} + '@apexdevtools/apex-parser@5.0.0-beta.5': + resolution: {integrity: sha512-t5UPBiTMMVUL+XnOLvGUARm52G/GgNMKwYxboNxFSent8f6j707za6qryt98iJ6gABTtRyINyBnLoBJmbfkXjg==} + engines: {node: '>=20.19.0 || >=22.12.0'} + bundledDependencies: + - antlr4 '@apexdevtools/vf-parser@1.1.0': resolution: {integrity: sha512-dP45Y3b4F0b8HosvGEMo6ugRx8yKhaz7h6eJh1cD/YvFxajk6x9Hfrtw63U2EKAstug6waBsT6QC7h+4USjBnA==} @@ -8213,10 +8215,7 @@ snapshots: antlr4ts: 0.5.0-alpha.4 node-dir: 0.1.17 - '@apexdevtools/apex-parser@4.4.1': - dependencies: - antlr4ts: 0.5.0-alpha.4 - node-dir: 0.1.17 + '@apexdevtools/apex-parser@5.0.0-beta.5': {} '@apexdevtools/vf-parser@1.1.0': dependencies: diff --git a/rolldown.config.ts b/rolldown.config.ts index 77d84fa3..c4ddc368 100644 --- a/rolldown.config.ts +++ b/rolldown.config.ts @@ -35,6 +35,22 @@ const getSwcOptions = (dirPath: string) => * This plugin converts the string literals to numeric char code arrays before the printer * sees them. Can be removed once the upstream fix fully covers CJS long strings. */ +/** + * Stubs @apexdevtools/apex-parser Check.js for browser builds. + * Check.js imports node:fs, node:path etc. for filesystem operations that are + * never used in the browser. Replace the module with an empty export. + */ +function stubApexParserCheck(): Plugin { + return { + name: 'stub-apex-parser-check', + load(id) { + if (id.includes('@apexdevtools/apex-parser') && id.endsWith('/Check.js')) { + return 'export {}'; + } + }, + }; +} + function preserveAntlrATN(): Plugin { const segmentPattern = /(\w+\._serializedATNSegment\d+)\s*=\s*("(?:[^"\\]|\\.)*"(?:\s*\+\s*"(?:[^"\\]|\\.)*")*)\s*;/g; @@ -91,13 +107,16 @@ export default defineConfig([ ], platform: 'browser', resolve: { - alias: { eventemitter3: path.resolve(__dirname, 'node_modules/eventemitter3/index.js') }, + alias: { + eventemitter3: path.resolve(__dirname, 'node_modules/eventemitter3/index.js'), + }, }, moduleTypes: { '.css': 'js', }, tsconfig: production ? './log-viewer/tsconfig.json' : './log-viewer/tsconfig-dev.json', plugins: [ + stubApexParserCheck(), nodePolyfills(), postcss({ extensions: ['.css', '.scss'], diff --git a/rollup.config.mjs b/rollup.config.mjs index bc56cbac..d40ce409 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -15,6 +15,22 @@ const __dirname = path.dirname(__filename); const production = process.env.NODE_ENV === 'production'; +/** + * Stubs @apexdevtools/apex-parser Check.js for browser builds. + * Check.js imports node:fs, node:path etc. for filesystem operations that are + * never used in the browser. Replace the module with an empty export. + */ +function stubApexParserCheck() { + return { + name: 'stub-apex-parser-check', + load(id) { + if (id.includes('@apexdevtools/apex-parser') && id.endsWith('/Check.js')) { + return 'export {}'; + } + }, + }; +} + console.log('Package mode:', production ? 'production' : 'development'); export default [ { @@ -36,7 +52,7 @@ export default [ }, ], }), - nodeResolve({ preferBuiltins: true }), + nodeResolve({ preferBuiltins: true, exportConditions: ['node'] }), commonjs(), json(), swc( @@ -92,6 +108,7 @@ export default [ }, ], plugins: [ + stubApexParserCheck(), alias({ entries: [ {