From 6f0770646257782e5f575207aadbab51f1f32b4b 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 | 32 +++++++++--- .../langium/test/utils/grammar-util.test.ts | 52 ++++++++++++++++++- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/packages/langium/src/utils/grammar-util.ts b/packages/langium/src/utils/grammar-util.ts index ef018bfc0..d84c44c4f 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. ******************************************************************************/ @@ -47,33 +47,49 @@ export function getHiddenRules(grammar: ast.Grammar) { * this function returns all rules of the specified grammar. */ export function getAllReachableRules(grammar: ast.Grammar, allTerminals: boolean): Set { - const ruleNames = new Set(); const entryRule = getEntryRule(grammar); if (!entryRule) { return new Set(grammar.rules); } const topMostRules = [entryRule as ast.AbstractRule].concat(getHiddenRules(grammar)); + const collectedRules = new Set(); for (const rule of topMostRules) { - ruleDfs(rule, ruleNames, allTerminals); + ruleDfs(rule, collectedRules, allTerminals); } const rules = new Set(); for (const rule of grammar.rules) { - if (ruleNames.has(rule.name) || (ast.isTerminalRule(rule) && rule.hidden)) { + if (collectedRules.has(rule) || ((ast.isTerminalRule(rule) && rule.hidden))) { rules.add(rule); } } + for (const rule of collectedRules) { + if (!rules.has(rule)) { + rules.add(rule); + } + } + return rules; } -function ruleDfs(rule: ast.AbstractRule, visitedSet: Set, allTerminals: boolean): void { - visitedSet.add(rule.name); +function ruleDfs(rule: ast.AbstractRule, visitedRules: Set , allTerminals: boolean): void { + 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 && !visitedRules.has(refRule)) { + ruleDfs(refRule, 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 && !visitedRules.has(refRule)) { + ruleDfs(refRule, visitedRules, allTerminals); + } + } } } }); diff --git a/packages/langium/test/utils/grammar-util.test.ts b/packages/langium/test/utils/grammar-util.test.ts index 5a4b7b46c..e8cb7823e 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,53 @@ describe('Grammar Utils', () => { expect(reachableRules).toContain('Ws'); }); + test('getAllReachableRules should return rules referenced in cross references', 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; + Uncalled: name=IDX; + Called: name=INT; + terminal INT returns number: /[0-9]+/; + terminal ID: /[A-Z][\\w_]*/; + terminal IDX: /[a-z][\\w_]*/; + `); + const grammar2 = await parse(` + grammar G2 + import './${Utils.basename(grammar1.uri)}' + entry B: ref=[A] s=STRING c=Called; + terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/; + `); + await services.shared.workspace.DocumentBuilder.build([grammar2, grammar1]); + // act + const reachableRules = [...getAllReachableRules(grammar2.parseResult.value, true)].map(r => r.name); + // assert + expect(reachableRules).toEqual(['B', 'STRING', 'ID', 'Called', 'INT' ]); + }); + + test('getAllReachableRules should not return unused rules', 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 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'); + }); + });