From 55f68cccc168a1d5d88101540fb5f51bb45a5583 Mon Sep 17 00:00:00 2001 From: Christian Dietrich Date: Tue, 15 Aug 2023 17:11:49 +0200 Subject: [PATCH] add implicit ID rule call for cross refs to getAllReachableRules Fixes #1151 Signed-off-by: Christian Dietrich --- packages/langium/src/utils/grammar-util.ts | 28 +++++++---- .../langium/test/utils/grammar-util.test.ts | 46 ++++++++++++++++++- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/packages/langium/src/utils/grammar-util.ts b/packages/langium/src/utils/grammar-util.ts index ef018bfc0..3a1831583 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. ******************************************************************************/ @@ -54,26 +54,38 @@ export function getAllReachableRules(grammar: ast.Grammar, allTerminals: boolean } const topMostRules = [entryRule as ast.AbstractRule].concat(getHiddenRules(grammar)); + const rules = new Set(); for (const rule of topMostRules) { - ruleDfs(rule, ruleNames, allTerminals); + ruleDfs(rule, ruleNames, rules, allTerminals); } - const rules = new Set(); + for (const rule of grammar.rules) { - if (ruleNames.has(rule.name) || (ast.isTerminalRule(rule) && rule.hidden)) { + if ((ast.isTerminalRule(rule) && rule.hidden)) { rules.add(rule); } } return rules; } -function ruleDfs(rule: ast.AbstractRule, visitedSet: Set, allTerminals: boolean): void { - visitedSet.add(rule.name); +function ruleDfs(rule: ast.AbstractRule, visitedRuleNames: Set, visitedRules: Set , allTerminals: boolean): void { + visitedRuleNames.add(rule.name); + visitedRules.add(rule) 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); + if (refRule && !visitedRuleNames.has(refRule.name)) { + ruleDfs(refRule, visitedRuleNames, visitedRules, allTerminals); + } + } else if (ast.isCrossReference(node)) { + const term = getCrossReferenceTerminal(node) + if (term !== undefined) { + if (ast.isRuleCall(term) || (allTerminals && ast.isTerminalRuleCall(term))) { + const refRule = term.rule.ref; + if (refRule && !visitedRuleNames.has(refRule.name)) { + ruleDfs(refRule, visitedRuleNames, visitedRules, allTerminals); + } + } } } }); diff --git a/packages/langium/test/utils/grammar-util.test.ts b/packages/langium/test/utils/grammar-util.test.ts index 5a4b7b46c..9e01ee402 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,6 +8,7 @@ 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); @@ -36,4 +37,47 @@ describe('Grammar Utils', () => { 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 + entry 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(grammar2.parseResult.value, true)].map(r => r.name); + // assert + expect(reachableRules).toContain('ID'); + }); + + test('ID not implicit called should not be returned by getAllReachableRules', async () => { + // no implicit ID rule call in cross ref + // [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 + entry A: + 'A' name=ID; + Other: name=STRING; + 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(grammar2.parseResult.value, true)].map(r => r.name); + + // assert + expect(reachableRules).not.toContain('STRING'); + }); + });