Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how

## [Unreleased]
- Added: Generator command for a ViewModel class
- Added: Jump-to-definition for magento modules (in module.xml and routes.xml)
- Fixed: Method plugin hover messages are now grouped and include a link to di.xml

## [1.3.1] - 2025-03-23
Expand Down
36 changes: 36 additions & 0 deletions src/common/xml/XmlDocumentParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DocumentCstNode, parse } from '@xml-tools/parser';
import DocumentCache from 'cache/DocumentCache';
import PhpParser from 'parser/php/Parser';
import { TextDocument } from 'vscode';
import { buildAst, XMLDocument } from '@xml-tools/ast';

export interface TokenData {
cst: DocumentCstNode;
// xml-tools parser doesnt have an exported type for this
tokenVector: any[];
ast: XMLDocument;
}

class XmlDocumentParser {
protected readonly parser: PhpParser;

constructor() {
this.parser = new PhpParser();
}

public async parse(document: TextDocument): Promise<TokenData> {
const cacheKey = `xml-file`;

if (DocumentCache.has(document, cacheKey)) {
return DocumentCache.get(document, cacheKey);
}

const { cst, tokenVector } = parse(document.getText());
const ast = buildAst(cst as DocumentCstNode, tokenVector);
const tokenData: TokenData = { cst: cst as DocumentCstNode, tokenVector, ast };
DocumentCache.set(document, cacheKey, tokenData);
return tokenData;
}
}

export default new XmlDocumentParser();
43 changes: 43 additions & 0 deletions src/definition/XmlDefinitionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { minimatch } from 'minimatch';
import {
CancellationToken,
DefinitionProvider,
LocationLink,
Position,
TextDocument,
} from 'vscode';
import { getSuggestions, SuggestionProviders } from '@xml-tools/content-assist';
import XmlDocumentParser from 'common/xml/XmlDocumentParser';

export abstract class XmlDefinitionProvider implements DefinitionProvider {
public abstract getFilePatterns(): string[];
public abstract getDefinitionProviders(): SuggestionProviders<LocationLink>;

public async provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<LocationLink[]> {
if (!this.canProvideDefinition(document)) {
return [];
}

const { cst, tokenVector, ast } = await XmlDocumentParser.parse(document);

const definitions = getSuggestions({
ast,
cst,
tokenVector,
offset: document.offsetAt(position),
providers: this.getDefinitionProviders(),
});

return definitions.filter(definition => definition.targetUri.fsPath !== document.uri.fsPath);
}

private canProvideDefinition(document: TextDocument): boolean {
return this.getFilePatterns().some(pattern =>
minimatch(document.uri.fsPath, pattern, { matchBase: true })
);
}
}
59 changes: 59 additions & 0 deletions src/definition/XmlModuleDefinitionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AttributeValueCompletionOptions, SuggestionProviders } from '@xml-tools/content-assist';
import { XmlDefinitionProvider } from './XmlDefinitionProvider';
import { LocationLink, Uri, Range } from 'vscode';
import ModuleIndexer from 'indexer/module/ModuleIndexer';
import IndexManager from 'indexer/IndexManager';

export class XmlModuleDefinitionProvider extends XmlDefinitionProvider {
public getFilePatterns(): string[] {
return ['**/etc/module.xml', '**/etc/**/routes.xml'];
}

public getDefinitionProviders(): SuggestionProviders<LocationLink> {
return {
attributeValue: [this.getModuleDefinitions],
};
}

private getModuleDefinitions({
element,
attribute,
}: AttributeValueCompletionOptions<undefined>): LocationLink[] {
if (element.name !== 'module' || attribute.key !== 'name') {
return [];
}

const moduleName = attribute.value;

if (!moduleName) {
return [];
}

const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY);

if (!moduleIndexData) {
return [];
}

const module = moduleIndexData.getModule(moduleName);

if (!module) {
return [];
}

const moduleXmlUri = Uri.file(module.moduleXmlPath);

return [
{
targetUri: moduleXmlUri,
targetRange: new Range(0, 0, 0, 0),
originSelectionRange: new Range(
attribute.position.startLine - 1,
attribute.position.startColumn + 5,
attribute.position.endLine - 1,
attribute.position.endColumn - 1
),
},
];
}
}
4 changes: 3 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Magento from 'util/Magento';
import { WorkspaceFolder } from 'vscode';
import Logger from 'util/Logger';
import { Command } from 'command/Command';
import { XmlModuleDefinitionProvider } from 'definition/XmlModuleDefinitionProvider';

// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
Expand Down Expand Up @@ -90,7 +91,8 @@ export async function activate(context: vscode.ExtensionContext) {

// definition providers
context.subscriptions.push(
vscode.languages.registerDefinitionProvider('xml', new XmlClasslikeDefinitionProvider())
vscode.languages.registerDefinitionProvider('xml', new XmlClasslikeDefinitionProvider()),
vscode.languages.registerDefinitionProvider('xml', new XmlModuleDefinitionProvider())
);

// codelens providers
Expand Down
1 change: 1 addition & 0 deletions src/indexer/module/ModuleIndexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default class ModuleIndexer extends Indexer<Module> {
name: moduleName,
version: setupVersion,
sequence: sequence.map((module: any) => module['@_name']),
moduleXmlPath: uri.fsPath,
path: Uri.joinPath(uri, '..', '..').fsPath,
location: uri.fsPath.includes('vendor') ? 'vendor' : 'app',
};
Expand Down
1 change: 1 addition & 0 deletions src/indexer/module/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export interface Module {
version?: string;
sequence: string[];
path: string;
moduleXmlPath: string;
location: 'vendor' | 'app';
}