Skip to content

Commit 0082b69

Browse files
committed
feat: observer decorations; adjusted index data types
1 parent 9a6532f commit 0082b69

24 files changed

+372
-114
lines changed

resources/icons/observer.svg

Lines changed: 6 additions & 0 deletions
Loading

src/command/CopyMagentoPathCommand.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Command } from 'command/Command';
22
import IndexManager from 'indexer/IndexManager';
3-
import { ModuleIndexData } from 'indexer/module/ModuleIndexData';
43
import ModuleIndexer from 'indexer/module/ModuleIndexer';
54
import { Uri, window, env } from 'vscode';
65

@@ -32,14 +31,12 @@ export default class CopyMagentoPathCommand extends Command {
3231
return;
3332
}
3433

35-
const moduleIndex = IndexManager.getIndexData(ModuleIndexer.KEY);
34+
const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY);
3635

37-
if (!moduleIndex) {
36+
if (!moduleIndexData) {
3837
return;
3938
}
4039

41-
const moduleIndexData = new ModuleIndexData(moduleIndex);
42-
4340
const module = moduleIndexData.getModuleByUri(file);
4441

4542
if (!module) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { EventsIndexData } from 'indexer/events/EventsIndexData';
2+
import EventsIndexer from 'indexer/events/EventsIndexer';
3+
import IndexManager from 'indexer/IndexManager';
4+
import { PhpClass } from 'parser/php/PhpClass';
5+
import { PhpFile } from 'parser/php/PhpFile';
6+
import { Event } from 'indexer/events/types';
7+
import { NodeKind } from 'parser/php/Parser';
8+
import { Call } from 'php-parser';
9+
import { Memoize } from 'typescript-memoize';
10+
11+
export default class ObserverClassInfo {
12+
private eventsIndexData: EventsIndexData | undefined;
13+
constructor(private readonly phpFile: PhpFile) {}
14+
15+
public getObserverEvents(): Event[] {
16+
const phpClass = this.getClass();
17+
18+
if (!phpClass) {
19+
return [];
20+
}
21+
22+
const fqn = phpClass.namespace + '\\' + phpClass.name;
23+
24+
if (!fqn) {
25+
return [];
26+
}
27+
28+
const eventsIndexData = IndexManager.getIndexData(EventsIndexer.KEY);
29+
30+
if (!eventsIndexData) {
31+
return [];
32+
}
33+
34+
const events = eventsIndexData.findEventsByObserverInstance(fqn);
35+
36+
return events;
37+
}
38+
39+
@Memoize()
40+
public getEventDispatchCalls(): Call[] {
41+
const eventsIndexData = IndexManager.getIndexData(EventsIndexer.KEY);
42+
43+
if (!eventsIndexData) {
44+
return [];
45+
}
46+
47+
const phpClass = this.getClass();
48+
49+
if (!phpClass) {
50+
return [];
51+
}
52+
53+
const calls = phpClass.searchAst(NodeKind.Call);
54+
const dispatchCalls = calls.filter((call: any) => {
55+
if (call.what?.offset?.name === 'dispatch') {
56+
return true;
57+
}
58+
59+
return false;
60+
});
61+
62+
if (dispatchCalls.length === 0) {
63+
return [];
64+
}
65+
66+
const eventNames = eventsIndexData.getEventNames();
67+
68+
const eventDispatchCalls = dispatchCalls.filter((call: Call) => {
69+
if (call.arguments.length === 0) {
70+
return false;
71+
}
72+
73+
const firstArgument = call.arguments[0];
74+
75+
if (!firstArgument || firstArgument.kind !== NodeKind.String) {
76+
return false;
77+
}
78+
79+
const eventName = (firstArgument as any).value;
80+
81+
if (eventNames.includes(eventName)) {
82+
return true;
83+
}
84+
85+
return false;
86+
});
87+
88+
return eventDispatchCalls;
89+
}
90+
91+
private getClass(): PhpClass | undefined {
92+
const phpClass = this.phpFile.classes[0];
93+
94+
if (!phpClass) {
95+
return undefined;
96+
}
97+
98+
return phpClass;
99+
}
100+
}

src/common/php/PluginSubjectInfo.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@ export default class PluginSubjectInfo {
1010
constructor(private readonly phpFile: PhpFile) {}
1111

1212
public getPlugins(phpClasslike: PhpClass | PhpInterface) {
13-
const diIndex = IndexManager.getIndexData(DiIndexer.KEY);
13+
const diIndexData = IndexManager.getIndexData(DiIndexer.KEY);
1414

15-
if (!diIndex) {
15+
if (!diIndexData) {
1616
return [];
1717
}
1818

19-
const diIndexData = new DiIndexData(diIndex);
20-
2119
const fqn = phpClasslike.namespace + '\\' + phpClasslike.name;
2220

2321
if (!fqn) {
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { DecorationOptions, TextEditorDecorationType, Uri, window } from 'vscode';
2+
import path from 'path';
3+
import TextDocumentDecorationProvider from './TextDocumentDecorationProvider';
4+
import { PhpClass } from 'parser/php/PhpClass';
5+
import { PhpInterface } from 'parser/php/PhpInterface';
6+
import ObserverClassInfo from 'common/php/ObserverClassInfo';
7+
import PhpDocumentParser from 'common/php/PhpDocumentParser';
8+
import { ClasslikeInfo } from 'common/php/ClasslikeInfo';
9+
import MarkdownMessageBuilder from 'common/MarkdownMessageBuilder';
10+
import Position from 'util/Position';
11+
import { NodeKind } from 'parser/php/Parser';
12+
import IndexManager from 'indexer/IndexManager';
13+
import EventsIndexer from 'indexer/events/EventsIndexer';
14+
15+
export default class ObserverInstanceDecorationProvider extends TextDocumentDecorationProvider {
16+
public getType(): TextEditorDecorationType {
17+
return window.createTextEditorDecorationType({
18+
gutterIconPath: path.join(__dirname, 'resources', 'icons', 'observer.svg'),
19+
gutterIconSize: '80%',
20+
borderColor: 'rgba(0, 188, 202, 0.5)',
21+
borderStyle: 'dotted',
22+
borderWidth: '0 0 1px 0',
23+
});
24+
}
25+
26+
public async getDecorations(): Promise<DecorationOptions[]> {
27+
const decorations: DecorationOptions[] = [];
28+
const phpFile = await PhpDocumentParser.parse(this.document);
29+
30+
const classLikeNode: PhpClass | PhpInterface | undefined =
31+
phpFile.classes[0] || phpFile.interfaces[0];
32+
33+
if (!classLikeNode) {
34+
return decorations;
35+
}
36+
37+
const observerClassInfo = new ObserverClassInfo(phpFile);
38+
const classlikeInfo = new ClasslikeInfo(phpFile);
39+
40+
if (this.isObserverInstance(classlikeInfo)) {
41+
decorations.push(...this.getObserverInstanceDecorations(observerClassInfo, classlikeInfo));
42+
}
43+
44+
decorations.push(...this.getEventDispatchDecorations(observerClassInfo));
45+
46+
return decorations;
47+
}
48+
49+
private getEventDispatchDecorations(observerClassInfo: ObserverClassInfo): DecorationOptions[] {
50+
const decorations: DecorationOptions[] = [];
51+
52+
const eventDispatchCalls = observerClassInfo.getEventDispatchCalls();
53+
54+
if (eventDispatchCalls.length === 0) {
55+
return decorations;
56+
}
57+
58+
const eventsIndexData = IndexManager.getIndexData(EventsIndexer.KEY);
59+
60+
if (!eventsIndexData) {
61+
return decorations;
62+
}
63+
64+
for (const eventDispatchCall of eventDispatchCalls) {
65+
const args = eventDispatchCall.arguments;
66+
67+
if (args.length === 0) {
68+
continue;
69+
}
70+
71+
const firstArg = args[0];
72+
73+
if (!firstArg || firstArg.kind !== NodeKind.String || !firstArg.loc) {
74+
continue;
75+
}
76+
77+
const eventName = (firstArg as any).value;
78+
79+
const event = eventsIndexData.getEventByName(eventName);
80+
81+
if (!event) {
82+
continue;
83+
}
84+
85+
const range = Position.phpAstLocationToVsCodeRange(firstArg.loc);
86+
const hoverMessage = MarkdownMessageBuilder.create('Event observers');
87+
88+
for (const observer of event.observers) {
89+
hoverMessage.appendMarkdown(`- [${observer.instance}](${Uri.file(event.diPath)})\n`);
90+
}
91+
92+
decorations.push({
93+
range,
94+
hoverMessage: hoverMessage.build(),
95+
});
96+
}
97+
98+
return decorations;
99+
}
100+
101+
private getObserverInstanceDecorations(
102+
observerClassInfo: ObserverClassInfo,
103+
classlikeInfo: ClasslikeInfo
104+
): DecorationOptions[] {
105+
const decorations: DecorationOptions[] = [];
106+
107+
const observerEvents = observerClassInfo.getObserverEvents();
108+
109+
if (observerEvents.length === 0) {
110+
return decorations;
111+
}
112+
113+
const nameRange = classlikeInfo.getNameRange();
114+
115+
if (!nameRange) {
116+
return decorations;
117+
}
118+
119+
const hoverMessage = MarkdownMessageBuilder.create('Observer Events');
120+
121+
for (const event of observerEvents) {
122+
hoverMessage.appendMarkdown(`- [${event.name}](${Uri.file(event.diPath)})\n`);
123+
}
124+
125+
decorations.push({
126+
range: nameRange,
127+
hoverMessage: hoverMessage.build(),
128+
});
129+
130+
return decorations;
131+
}
132+
133+
private isObserverInstance(classlikeInfo: ClasslikeInfo): boolean {
134+
const imp = classlikeInfo.getImplements();
135+
136+
return (
137+
imp.includes('Magento\\Framework\\Event\\ObserverInterface') ||
138+
imp.includes('ObserverInterface')
139+
);
140+
}
141+
}

src/decorator/PluginClassDecorationProvider.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import IndexManager from 'indexer/IndexManager';
2222
import AutoloadNamespaceIndexer from 'indexer/autoload-namespace/AutoloadNamespaceIndexer';
2323
import { AutoloadNamespaceIndexData } from 'indexer/autoload-namespace/AutoloadNamespaceIndexData';
2424
import { DiPlugin } from 'indexer/di/types';
25+
import PhpDocumentParser from 'common/php/PhpDocumentParser';
26+
import Common from 'util/Common';
2527

2628
export default class PluginClassDecorationProvider extends TextDocumentDecorationProvider {
2729
public getType(): TextEditorDecorationType {
@@ -36,8 +38,7 @@ export default class PluginClassDecorationProvider extends TextDocumentDecoratio
3638

3739
public async getDecorations(): Promise<DecorationOptions[]> {
3840
const decorations: DecorationOptions[] = [];
39-
const parser = new PhpParser();
40-
const phpFile = await parser.parseDocument(this.document);
41+
const phpFile = await PhpDocumentParser.parse(this.document);
4142

4243
const classLikeNode: PhpClass | PhpInterface | undefined =
4344
phpFile.classes[0] || phpFile.interfaces[0];
@@ -60,14 +61,12 @@ export default class PluginClassDecorationProvider extends TextDocumentDecoratio
6061
return decorations;
6162
}
6263

63-
const namespaceIndex = IndexManager.getIndexData(AutoloadNamespaceIndexer.KEY);
64+
const namespaceIndexData = IndexManager.getIndexData(AutoloadNamespaceIndexer.KEY);
6465

65-
if (!namespaceIndex) {
66+
if (!namespaceIndexData) {
6667
return decorations;
6768
}
6869

69-
const namespaceIndexData = new AutoloadNamespaceIndexData(namespaceIndex);
70-
7170
const hoverMessage = await this.getInterceptorHoverMessage(classPlugins, namespaceIndexData);
7271

7372
decorations.push({
@@ -84,7 +83,7 @@ export default class PluginClassDecorationProvider extends TextDocumentDecoratio
8483
return;
8584
}
8685

87-
const pluginPhpFile = await parser.parse(fileUri);
86+
const pluginPhpFile = await PhpDocumentParser.parseUri(this.document, fileUri);
8887
const pluginInfo = new PluginInfo(pluginPhpFile);
8988
const pluginMethods = pluginInfo.getPluginMethods(pluginPhpFile.classes[0]);
9089

src/definition/XmlClasslikeDefinitionProvider.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ClasslikeInfo } from 'common/php/ClasslikeInfo';
22
import PhpDocumentParser from 'common/php/PhpDocumentParser';
33
import PhpNamespace from 'common/PhpNamespace';
4-
import { AutoloadNamespaceIndexData } from 'indexer/autoload-namespace/AutoloadNamespaceIndexData';
54
import AutoloadNamespaceIndexer from 'indexer/autoload-namespace/AutoloadNamespaceIndexer';
65
import IndexManager from 'indexer/IndexManager';
76
import {
@@ -15,8 +14,6 @@ import {
1514
} from 'vscode';
1615

1716
export class XmlClasslikeDefinitionProvider implements DefinitionProvider {
18-
private namespaceIndexData: AutoloadNamespaceIndexData | undefined;
19-
2017
public async provideDefinition(
2118
document: TextDocument,
2219
position: Position,
@@ -30,7 +27,7 @@ export class XmlClasslikeDefinitionProvider implements DefinitionProvider {
3027

3128
const word = document.getText(range);
3229

33-
const namespaceIndexData = this.getNamespaceIndexData();
30+
const namespaceIndexData = IndexManager.getIndexData(AutoloadNamespaceIndexer.KEY);
3431

3532
if (!namespaceIndexData) {
3633
return null;
@@ -62,20 +59,6 @@ export class XmlClasslikeDefinitionProvider implements DefinitionProvider {
6259
];
6360
}
6461

65-
private getNamespaceIndexData(): AutoloadNamespaceIndexData | undefined {
66-
if (!this.namespaceIndexData) {
67-
const namespaceIndex = IndexManager.getIndexData(AutoloadNamespaceIndexer.KEY);
68-
69-
if (!namespaceIndex) {
70-
return undefined;
71-
}
72-
73-
this.namespaceIndexData = new AutoloadNamespaceIndexData(namespaceIndex);
74-
}
75-
76-
return this.namespaceIndexData;
77-
}
78-
7962
private async getClasslikeNameRange(
8063
document: TextDocument,
8164
classUri: Uri

0 commit comments

Comments
 (0)