diff --git a/packages/langium/src/utils/grammar-util.ts b/packages/langium/src/utils/grammar-util.ts index ef018bfc0..d46815442 100644 --- a/packages/langium/src/utils/grammar-util.ts +++ b/packages/langium/src/utils/grammar-util.ts @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright 2021-2022 TypeFox GmbH + * Copyright 2021-2023 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ @@ -69,12 +69,28 @@ export function getAllReachableRules(grammar: ast.Grammar, allTerminals: boolean function ruleDfs(rule: ast.AbstractRule, visitedSet: Set, allTerminals: boolean): void { visitedSet.add(rule.name); + console.log("xxxx", visitedSet) streamAllContents(rule).forEach(node => { if (ast.isRuleCall(node) || (allTerminals && ast.isTerminalRuleCall(node))) { const refRule = node.rule.ref; if (refRule && !visitedSet.has(refRule.name)) { ruleDfs(refRule, visitedSet, allTerminals); } + } else if (ast.isCrossReference(node)) { + // TODO why does this not work + const term = getCrossReferenceTerminal(node) + console.log(term) + if (term !== undefined) { + console.log(term) + if (ast.isRuleCall(term) || (allTerminals && ast.isTerminalRuleCall(term))) { + const refRule = term.rule.ref; + console.log(refRule?.name) + if (refRule && !visitedSet.has(refRule.name)) { + console.log("recurse") + ruleDfs(refRule, visitedSet, allTerminals); + } + } + } } }); } diff --git a/packages/langium/test/utils/grammar-util.test.ts b/packages/langium/test/utils/grammar-util.test.ts index 5a4b7b46c..9d45428b4 100644 --- a/packages/langium/test/utils/grammar-util.test.ts +++ b/packages/langium/test/utils/grammar-util.test.ts @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright 2022 TypeFox GmbH + * Copyright 2022-2023 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ @@ -8,32 +8,74 @@ import type { Grammar } from '../../src'; import { describe, expect, test } from 'vitest'; import { createLangiumGrammarServices, EmptyFileSystem, getAllReachableRules } from '../../src'; import { parseHelper } from '../../src/test'; +import { Utils } from 'vscode-uri'; const services = createLangiumGrammarServices(EmptyFileSystem); const parse = parseHelper(services.grammar); describe('Grammar Utils', () => { - test('Terminal fragment rule should be reachable when only used by hidden terminal rule', async () => { - // the actual bug was that the 'Ws' rule marked as unused - so a 'Error: Missing rule reference!' was thrown - // arrange - const input = ` - grammar HelloWorld + // test('Terminal fragment rule should be reachable when only used by hidden terminal rule', async () => { + // // the actual bug was that the 'Ws' rule marked as unused - so a 'Error: Missing rule reference!' was thrown + // // arrange + // const input = ` + // grammar HelloWorld - entry Model: Hello; + // entry Model: Hello; - Hello: greeting='Hello!'; + // Hello: greeting='Hello!'; - hidden terminal COMMON__WS: Ws+; - terminal fragment Ws: /[ \t\r\n\f]/; - `; - const output = await parse(input); + // hidden terminal COMMON__WS: Ws+; + // terminal fragment Ws: /[ \t\r\n\f]/; + // `; + // const output = await parse(input); + // // act + // const reachableRules = [...getAllReachableRules(output.parseResult.value, true)].map(r => r.name); + + // // assert + // expect(reachableRules).toContain('Ws'); + // }); + + test('ID implicit called should be returned by getAllReachableRules', async () => { + // [A] is short for [A:ID] thus the ID rule is needed by the parser and getAllReachableRules should return ID + const grammar1 = await parse(` + grammar G1 + return A: + 'A' name=ID; + terminal ID: /[_a-zA-Z][\\w_]*/; + `); + const grammar2 = await parse(` + grammar G2 + import './${Utils.basename(grammar1.uri)}' + entry B: ref=[A]; + `); + await services.shared.workspace.DocumentBuilder.build([grammar2, grammar1]); // act - const reachableRules = [...getAllReachableRules(output.parseResult.value, true)].map(r => r.name); + const reachableRules = [...getAllReachableRules(grammar2.parseResult.value, true)].map(r => r.name); // assert - expect(reachableRules).toContain('Ws'); + expect(reachableRules).toContain('ID'); }); + // test('ID not implicit called should not be returned by getAllReachableRules', async () => { + // // no implicit ID rule call in cross ref + // const input = ` + // grammar HelloWorld + + // entry Model: A|B; + // A: name=STRING; + // B: ref=[A:STRING]; + // terminal ID: /[_a-zA-Z][\w_]*/; + // terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/; + // `; + // const output = await parse(input); + + // // act + // const reachableRules = [...getAllReachableRules(output.parseResult.value, true)].map(r => r.name); + + // // assert + // expect(reachableRules).not.toContain('ID'); + // }); + });