Skip to content

Commit

Permalink
created own file with test cases for quick-fixes eclipse-langium#1309
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesMeierSE committed Feb 29, 2024
1 parent 9f238d3 commit 384b922
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 70 deletions.
4 changes: 2 additions & 2 deletions packages/langium/src/grammar/lsp/grammar-code-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ export class LangiumGrammarCodeActionProvider implements CodeActionProvider {
const cstNode = findLeafNodeAtOffset(rootCst, offset);
const rule = getContainerOfType(cstNode?.astNode, ast.isParserRule);
if (rule && rule.$cstNode) {
const isFixable = this.isReplaceableRule(rule) && this.isReplaceable(rule.definition);
if (isFixable) {
const isRuleReplaceable = this.isReplaceableRule(rule) && this.isReplaceable(rule.definition);
if (isRuleReplaceable) {
const newText = `type ${this.replaceRule(rule)} = ${this.replace(rule.definition)};`;
return {
title: 'Replace parser rule by type declaration',
Expand Down
1 change: 0 additions & 1 deletion packages/langium/src/grammar/validation/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,6 @@ export class LangiumGrammarValidator {
if (ast.isParserRule(rule) && parserRulesUsedByCrossReferences.has(rule)) {
accept('hint', 'This parser rule is not used for parsing, but referenced by cross-references. Consider to replace this rule by a type declaration.', {
node: rule,
// property: 'name',
data: diagnosticData(IssueCodes.ParserRuleToTypeDecl)
});
} else {
Expand Down
70 changes: 3 additions & 67 deletions packages/langium/test/grammar/grammar-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { AstUtils, EmptyFileSystem, GrammarAST } from 'langium';
import { IssueCodes, createLangiumGrammarServices } from 'langium/grammar';
import { clearDocuments, expectError, expectIssue, expectNoIssues, expectWarning, parseHelper, textDocumentParams, validationHelper } from 'langium/test';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { textBeforeParserRuleCrossReferences, textExpectedParserRuleCrossReferences } from './lsp/grammar-code-actions.test.js';

const services = createLangiumGrammarServices(EmptyFileSystem);
const parse = parseHelper(services.grammar);
Expand Down Expand Up @@ -430,80 +431,15 @@ describe('Unused rules validation', () => {
});
});

test('Parser rules used only as type in cross-references are correctly identified as used', async () => {
test('Parser rules used only as type in cross-references are not marked as unused, but with a hint suggesting a type declaration', async () => {
// this test case targets https://github.com/eclipse-langium/langium/issues/1309
const text = `
grammar ParserRulesOnlyForCrossReferences
entry Model:
(persons+=Neighbor | friends+=Friend | greetings+=Greeting)*;
Neighbor:
'neighbor' name=ID;
Friend:
'friend' name=ID;
Person: Neighbor | Friend; // 'Person' is used only for cross-references, not as parser rule
Greeting:
'Hello' person=[Person:ID] '!';
hidden terminal WS: /\\s+/;
terminal ID: /[_a-zA-Z][\\w_]*/;
`;
// check, that the expected validation hint is available
const validation = await validate(text);
const validation = await validate(textBeforeParserRuleCrossReferences);
expect(validation.diagnostics).toHaveLength(1);
const ruleWithHint = validation.document.parseResult.value.rules.find(e => e.name === 'Person')!;
expectIssue(validation, {
node: ruleWithHint,
// property: 'name',
severity: DiagnosticSeverity.Hint
});
// check, that the quick-fix is generated
const actionProvider = services.grammar.lsp.CodeActionProvider;
expect(actionProvider).toBeTruthy();
const currentAcctions = await actionProvider!.getCodeActions(validation.document, {
...textDocumentParams(validation.document),
range: validation.diagnostics[0].range,
context: {
diagnostics: validation.diagnostics,
triggerKind: 1 // explicitly triggered by users (or extensions)
}
});
// there is one quick-fix
expect(currentAcctions).toBeTruthy();
expect(Array.isArray(currentAcctions)).toBeTruthy();
expect(currentAcctions!.length).toBe(1);
expect(CodeAction.is(currentAcctions![0])).toBeTruthy();
const action: CodeAction = currentAcctions![0] as CodeAction;
// execute the found quick-fix
expect(action.title).toBe('Replace parser rule by type declaration');
const edits = action.edit?.changes![validation.document.textDocument.uri];
expect(edits).toBeTruthy();
const updatedText = TextDocument.applyEdits(validation.document.textDocument, edits!);

// check the result
const textExpected = `
grammar ParserRulesOnlyForCrossReferences
entry Model:
(persons+=Neighbor | friends+=Friend | greetings+=Greeting)*;
Neighbor:
'neighbor' name=ID;
Friend:
'friend' name=ID;
type Person = Neighbor | Friend; // 'Person' is used only for cross-references, not as parser rule
Greeting:
'Hello' person=[Person:ID] '!';
hidden terminal WS: /\\s+/;
terminal ID: /[_a-zA-Z][\\w_]*/;
`;
expect(updatedText).toBe(textExpected);
});

});
Expand Down
78 changes: 78 additions & 0 deletions packages/langium/test/grammar/lsp/grammar-code-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/******************************************************************************
* Copyright 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.
******************************************************************************/

import type { GrammarAST } from 'langium';
import { EmptyFileSystem, createLangiumGrammarServices } from 'langium';
import { textDocumentParams, validationHelper } from 'langium/test';
import { describe, expect, test } from 'vitest';
import { CodeAction } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';

const services = createLangiumGrammarServices(EmptyFileSystem);
const validate = validationHelper<GrammarAST.Grammar>(services.grammar);

export const textBeforeParserRuleCrossReferences = `
grammar ParserRulesOnlyForCrossReferences
entry Model: (persons+=Neighbor | friends+=Friend | greetings+=Greeting)*;
Neighbor: 'neighbor' name=ID;
Friend: 'friend' name=ID;
Person: Neighbor | Friend; // 'Person' is used only for cross-references, not as parser rule
Greeting: 'Hello' person=[Person:ID] '!';
hidden terminal WS: /\\s+/;
terminal ID: /[_a-zA-Z][\\w_]*/;
`;

export const textExpectedParserRuleCrossReferences = `
grammar ParserRulesOnlyForCrossReferences
entry Model: (persons+=Neighbor | friends+=Friend | greetings+=Greeting)*;
Neighbor: 'neighbor' name=ID;
Friend: 'friend' name=ID;
type Person = Neighbor | Friend; // 'Person' is used only for cross-references, not as parser rule
Greeting: 'Hello' person=[Person:ID] '!';
hidden terminal WS: /\\s+/;
terminal ID: /[_a-zA-Z][\\w_]*/;
`;

describe('Langium grammar quick-fixes for validations', () => {
test('Parser rules used only as type in cross-references are correctly identified as used', async () => {
// this test case targets https://github.com/eclipse-langium/langium/issues/1309
// check, that the expected validation hint is available
const validation = await validate(textBeforeParserRuleCrossReferences);
expect(validation.diagnostics).toHaveLength(1);
// check, that the quick-fix is generated
const actionProvider = services.grammar.lsp.CodeActionProvider;
expect(actionProvider).toBeTruthy();
const currentAcctions = await actionProvider!.getCodeActions(validation.document, {
...textDocumentParams(validation.document),
range: validation.diagnostics[0].range,
context: {
diagnostics: validation.diagnostics,
triggerKind: 1 // explicitly triggered by users (or extensions)
}
});
// there is one quick-fix
expect(currentAcctions).toBeTruthy();
expect(Array.isArray(currentAcctions)).toBeTruthy();
expect(currentAcctions!.length).toBe(1);
expect(CodeAction.is(currentAcctions![0])).toBeTruthy();
const action: CodeAction = currentAcctions![0] as CodeAction;
// execute the found quick-fix
expect(action.title).toBe('Replace parser rule by type declaration');
const edits = action.edit?.changes![validation.document.textDocument.uri];
expect(edits).toBeTruthy();
const updatedText = TextDocument.applyEdits(validation.document.textDocument, edits!);

// check the result after applying the quick-fix
expect(updatedText).toBe(textExpectedParserRuleCrossReferences);
const validationUpdated = await validate(updatedText);
expect(validationUpdated.diagnostics).toHaveLength(0);
});

});

0 comments on commit 384b922

Please sign in to comment.