diff --git a/.github/workflows/auto-bump-release.yml b/.github/workflows/auto-bump-release.yml index bde48dd..1bdb4cc 100644 --- a/.github/workflows/auto-bump-release.yml +++ b/.github/workflows/auto-bump-release.yml @@ -2,7 +2,7 @@ name: Auto bump version on: push: branches: - - release + - master jobs: version: diff --git a/package.json b/package.json index 58760eb..576d012 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,10 @@ { "command": "magento-toolbox.generateWebapiXmlFile", "title": "Magento Toolbox: Generate Webapi XML" + }, + { + "command": "magento-toolbox.generatePreference", + "title": "Magento Toolbox: Generate Preference" } ], "menus": { @@ -135,6 +139,10 @@ { "command": "magento-toolbox.generateContextPlugin", "when": "magento-toolbox.canGeneratePlugin" + }, + { + "command": "magento-toolbox.generatePreference", + "when": "magento-toolbox.canGeneratePreference" } ], "magento-toolbox.explorer-submenu": [ diff --git a/src/command/GenerateContextPluginCommand.ts b/src/command/GenerateContextPluginCommand.ts index afb137f..4b71692 100644 --- a/src/command/GenerateContextPluginCommand.ts +++ b/src/command/GenerateContextPluginCommand.ts @@ -76,7 +76,7 @@ export default class GenerateContextPluginCommand extends Command { await manager.generate(workspaceFolder.uri); await manager.writeFiles(); await manager.refreshIndex(workspaceFolder); - manager.openFirstFile(); + manager.openAllFiles(); } private resolvePluginMethod(pluginSubjectInfo: PluginSubjectInfo): PhpMethod | undefined { diff --git a/src/command/GenerateObserverCommand.ts b/src/command/GenerateObserverCommand.ts index 9b64674..9f408c3 100644 --- a/src/command/GenerateObserverCommand.ts +++ b/src/command/GenerateObserverCommand.ts @@ -59,6 +59,6 @@ export default class GenerateObserverCommand extends Command { await manager.generate(workspaceFolder.uri); await manager.writeFiles(); await manager.refreshIndex(workspaceFolder); - manager.openFirstFile(); + manager.openAllFiles(); } } diff --git a/src/command/GeneratePreferenceCommand.ts b/src/command/GeneratePreferenceCommand.ts new file mode 100644 index 0000000..a22debd --- /dev/null +++ b/src/command/GeneratePreferenceCommand.ts @@ -0,0 +1,79 @@ +import { Command } from 'command/Command'; +import WizzardClosedError from 'webview/error/WizzardClosedError'; +import FileGeneratorManager from 'generator/FileGeneratorManager'; +import Common from 'util/Common'; +import { TextDocument, Uri, window } from 'vscode'; +import IndexManager from 'indexer/IndexManager'; +import ModuleIndexer from 'indexer/module/ModuleIndexer'; +import PreferenceWizard, { PreferenceWizardData } from 'wizard/PreferenceWizard'; +import PreferenceClassGenerator from 'generator/preference/PreferenceClassGenerator'; +import PhpDocumentParser from 'common/php/PhpDocumentParser'; +import { ClasslikeInfo } from 'common/php/ClasslikeInfo'; +import PreferenceDiGenerator from 'generator/preference/PreferenceDiGenerator'; + +export default class GeneratePreferenceCommand extends Command { + constructor() { + super('magento-toolbox.generatePreference'); + } + + public async execute(uri?: Uri): Promise { + const moduleIndex = IndexManager.getIndexData(ModuleIndexer.KEY); + let contextModule: string | undefined; + + const contextUri = uri || window.activeTextEditor?.document.uri; + + if (moduleIndex && contextUri) { + const module = moduleIndex.getModuleByUri(contextUri); + + if (module) { + contextModule = module.name; + } + } + + const parentClassName = await this.getParentClassName(window.activeTextEditor?.document); + + const preferenceWizard = new PreferenceWizard(); + + let data: PreferenceWizardData; + + try { + data = await preferenceWizard.show(parentClassName, contextModule); + } catch (error) { + if (error instanceof WizzardClosedError) { + return; + } + + throw error; + } + + const manager = new FileGeneratorManager([ + new PreferenceClassGenerator(data), + new PreferenceDiGenerator(data), + ]); + + const workspaceFolder = Common.getActiveWorkspaceFolder(); + + if (!workspaceFolder) { + window.showErrorMessage('No active workspace folder'); + return; + } + + await manager.generate(workspaceFolder.uri); + await manager.writeFiles(); + await manager.refreshIndex(workspaceFolder); + manager.openAllFiles(); + } + + private async getParentClassName( + document: TextDocument | undefined + ): Promise { + if (!document) { + return undefined; + } + + const phpFile = await PhpDocumentParser.parse(document); + const info = new ClasslikeInfo(phpFile); + + return info.getNamespace(); + } +} diff --git a/src/common/Context.ts b/src/common/Context.ts index be22b02..ebeef39 100644 --- a/src/common/Context.ts +++ b/src/common/Context.ts @@ -4,6 +4,7 @@ import CopyMagentoPathCommand from 'command/CopyMagentoPathCommand'; export interface EditorContext { canGeneratePlugin: boolean; + canGeneratePreference: boolean; supportedMagentoPathExtensions: string[]; } @@ -25,6 +26,7 @@ class Context { await this.setContext({ ...this.editorContext, canGeneratePlugin: await this.canGeneratePlugin(editor), + canGeneratePreference: await this.canGeneratePreference(editor), }); } @@ -43,6 +45,7 @@ class Context { public getDefaultContext(): EditorContext { return { canGeneratePlugin: false, + canGeneratePreference: false, supportedMagentoPathExtensions: [ ...CopyMagentoPathCommand.TEMPLATE_EXTENSIONS, ...CopyMagentoPathCommand.WEB_EXTENSIONS, @@ -83,6 +86,15 @@ class Context { return false; } + + private async canGeneratePreference(editor: TextEditor): Promise { + if (editor.document.languageId !== 'php') { + return false; + } + + const phpFile = await PhpDocumentParser.parse(editor.document); + return phpFile.classes.length > 0 || phpFile.interfaces.length > 0; + } } export default new Context(); diff --git a/src/common/PhpNamespace.ts b/src/common/PhpNamespace.ts index a4dd642..b488c67 100644 --- a/src/common/PhpNamespace.ts +++ b/src/common/PhpNamespace.ts @@ -25,12 +25,17 @@ export default class PhpNamespace { return this.parts.join(PhpNamespace.NS_SEPARATOR); } - public append(part: string | PhpNamespace): PhpNamespace { - if (typeof part === 'string') { - return new PhpNamespace([...this.parts, part]); - } - - return new PhpNamespace([...this.parts, ...part.getParts()]); + public append(...parts: (string | PhpNamespace)[]): PhpNamespace { + return new PhpNamespace([ + ...this.parts, + ...parts.flatMap(part => { + if (typeof part === 'string') { + return [part]; + } + + return part.getParts(); + }), + ]); } public prepend(part: string | PhpNamespace): PhpNamespace { diff --git a/src/extension.ts b/src/extension.ts index 9905273..71fe3ea 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,6 +23,7 @@ import GenerateGraphqlSchemaFileCommand from 'command/GenerateGraphqlSchemaFile' import GenerateRoutesXmlFileCommand from 'command/GenerateRoutesXmlFileCommand'; import GenerateAclXmlFileCommand from 'command/GenerateAclXmlFileCommand'; import GenerateDiXmlFileCommand from 'command/GenerateDiXmlFileCommand'; +import GeneratePreferenceCommand from 'command/GeneratePreferenceCommand'; // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -42,6 +43,7 @@ export async function activate(context: vscode.ExtensionContext) { GenerateRoutesXmlFileCommand, GenerateAclXmlFileCommand, GenerateDiXmlFileCommand, + GeneratePreferenceCommand, ]; ExtensionState.init(context); diff --git a/src/generator/ModuleFileGenerator.ts b/src/generator/ModuleFileGenerator.ts deleted file mode 100644 index 465dc16..0000000 --- a/src/generator/ModuleFileGenerator.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Uri } from 'vscode'; -import FileGenerator from './FileGenerator'; - -export default abstract class ModuleFileGenerator extends FileGenerator { - public getModuleDirectory(vendor: string, module: string, baseUri: Uri): Uri { - return Uri.joinPath(baseUri, 'app', 'code', vendor, module); - } - - public getModuleName(vendor: string, module: string): string { - return `${vendor}_${module}`; - } - - public getModuleNamespace(vendor: string, module: string): string { - return `${vendor}\\${module}`; - } -} diff --git a/src/generator/TemplateGenerator.ts b/src/generator/TemplateGenerator.ts index 0059a6b..dc8dc9e 100644 --- a/src/generator/TemplateGenerator.ts +++ b/src/generator/TemplateGenerator.ts @@ -1,9 +1,9 @@ import { Uri } from 'vscode'; -import ModuleFileGenerator from './ModuleFileGenerator'; +import FileGenerator from './FileGenerator'; import GeneratedFile from './GeneratedFile'; import GenerateFromTemplate from './util/GenerateFromTemplate'; -export default class TemplateGenerator extends ModuleFileGenerator { +export default class TemplateGenerator extends FileGenerator { public constructor( protected fileName: string, protected templateName: string, diff --git a/src/generator/block/BlockClassGenerator.ts b/src/generator/block/BlockClassGenerator.ts index 3e8838c..faf113b 100644 --- a/src/generator/block/BlockClassGenerator.ts +++ b/src/generator/block/BlockClassGenerator.ts @@ -1,16 +1,12 @@ import FileHeader from 'common/php/FileHeader'; +import FileGenerator from 'generator/FileGenerator'; import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; import { PhpFile, PsrPrinter } from 'node-php-generator'; +import Magento from 'util/Magento'; import { Uri } from 'vscode'; import { BlockWizardData } from 'wizard/BlockWizard'; -/** - * This is file was generated by the Magento Toolbox extension. - * %module% is the name of the module. - */ - -export default class BlockClassGenerator extends ModuleFileGenerator { +export default class BlockClassGenerator extends FileGenerator { private static readonly BLOCK_CLASS_PARENT = 'Magento\\Framework\\View\\Element\\Template'; public constructor(protected data: BlockWizardData) { @@ -20,7 +16,7 @@ export default class BlockClassGenerator extends ModuleFileGenerator { public async generate(workspaceUri: Uri): Promise { const [vendor, module] = this.data.module.split('_'); const namespaceParts = [vendor, module, this.data.path]; - const moduleDirectory = this.getModuleDirectory(vendor, module, workspaceUri); + const moduleDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri); const header = FileHeader.getHeader(this.data.module); diff --git a/src/generator/module/ModuleComposerGenerator.ts b/src/generator/module/ModuleComposerGenerator.ts index dbd6c99..1647180 100644 --- a/src/generator/module/ModuleComposerGenerator.ts +++ b/src/generator/module/ModuleComposerGenerator.ts @@ -1,16 +1,17 @@ +import FileGenerator from 'generator/FileGenerator'; import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; +import Magento from 'util/Magento'; import { Uri } from 'vscode'; import { ModuleWizardComposerData } from 'wizard/ModuleWizard'; -export default class ModuleComposerGenerator extends ModuleFileGenerator { +export default class ModuleComposerGenerator extends FileGenerator { public constructor(protected data: ModuleWizardComposerData) { super(); } public async generate(workspaceUri: Uri): Promise { const content = this.getComposerContent(); - const moduleDirectory = this.getModuleDirectory( + const moduleDirectory = Magento.getModuleDirectory( this.data.vendor, this.data.module, workspaceUri @@ -20,6 +21,8 @@ export default class ModuleComposerGenerator extends ModuleFileGenerator { } public getComposerContent(): string { + const namespace = Magento.getModuleNamespace(this.data.vendor, this.data.module); + const object: any = { name: this.data.composerName, description: this.data.composerDescription, @@ -30,7 +33,7 @@ export default class ModuleComposerGenerator extends ModuleFileGenerator { autoload: { files: ['registration.php'], psr4: { - [`${this.data.vendor}\\${this.data.module}\\`]: '', + [namespace.toString() + '\\']: 'src/', }, }, }; diff --git a/src/generator/module/ModuleLicenseGenerator.ts b/src/generator/module/ModuleLicenseGenerator.ts index 4638d58..af0239b 100644 --- a/src/generator/module/ModuleLicenseGenerator.ts +++ b/src/generator/module/ModuleLicenseGenerator.ts @@ -1,5 +1,6 @@ import GeneratedFile from 'generator/GeneratedFile'; import TemplateGenerator from 'generator/TemplateGenerator'; +import Magento from 'util/Magento'; import { Uri } from 'vscode'; import { ModuleWizardComposerData, ModuleWizardData } from 'wizard/ModuleWizard'; @@ -9,7 +10,7 @@ export default class ModuleLicenseGenerator extends TemplateGenerator { } public async generate(workspaceUri: Uri): Promise { - const moduleDirectory = this.getModuleDirectory( + const moduleDirectory = Magento.getModuleDirectory( this.data.vendor, this.data.module, workspaceUri diff --git a/src/generator/module/ModuleRegistrationGenerator.ts b/src/generator/module/ModuleRegistrationGenerator.ts index d351f4c..fa69764 100644 --- a/src/generator/module/ModuleRegistrationGenerator.ts +++ b/src/generator/module/ModuleRegistrationGenerator.ts @@ -1,22 +1,23 @@ -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; import { ModuleWizardComposerData, ModuleWizardData } from 'wizard/ModuleWizard'; -import { Literal, PhpFile, PsrPrinter } from 'node-php-generator'; +import { PhpFile, PsrPrinter } from 'node-php-generator'; import { Uri } from 'vscode'; import GeneratedFile from 'generator/GeneratedFile'; +import Magento from 'util/Magento'; +import FileGenerator from 'generator/FileGenerator'; -export default class ModuleRegistrationGenerator extends ModuleFileGenerator { +export default class ModuleRegistrationGenerator extends FileGenerator { public constructor(protected data: ModuleWizardData | ModuleWizardComposerData) { super(); } public async generate(workspaceUri: Uri): Promise { const registrationContent = this.getRegistrationContent(); - const moduleDirectory = this.getModuleDirectory( + const registrationFileUri = Magento.getModuleDirectory( this.data.vendor, this.data.module, - workspaceUri + workspaceUri, + 'registration.php' ); - const registrationFileUri = Uri.joinPath(moduleDirectory, 'registration.php'); return new GeneratedFile(registrationFileUri, registrationContent); } @@ -26,7 +27,7 @@ export default class ModuleRegistrationGenerator extends ModuleFileGenerator { file.setStrictTypes(true); file.addUse('Magento\\Framework\\Component\\ComponentRegistrar'); - const moduleName = this.getModuleName(this.data.vendor, this.data.module); + const moduleName = Magento.getModuleName(this.data.vendor, this.data.module); let content = printer.printFile(file); diff --git a/src/generator/module/ModuleXmlGenerator.ts b/src/generator/module/ModuleXmlGenerator.ts index 316bf53..af769e5 100644 --- a/src/generator/module/ModuleXmlGenerator.ts +++ b/src/generator/module/ModuleXmlGenerator.ts @@ -1,10 +1,11 @@ import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; import XmlGenerator from 'generator/XmlGenerator'; import { Uri } from 'vscode'; import { ModuleWizardComposerData, ModuleWizardData } from 'wizard/ModuleWizard'; +import FileGenerator from '../FileGenerator'; +import Magento from 'util/Magento'; -export default class ModuleXmlGenerator extends ModuleFileGenerator { +export default class ModuleXmlGenerator extends FileGenerator { public constructor(protected data: ModuleWizardData | ModuleWizardComposerData) { super(); } @@ -12,19 +13,18 @@ export default class ModuleXmlGenerator extends ModuleFileGenerator { public async generate(workspaceUri: Uri): Promise { const xmlContent = this.getXmlContent(); - const moduleDirectory = this.getModuleDirectory( + const moduleFile = Magento.getModuleDirectory( this.data.vendor, this.data.module, - workspaceUri + workspaceUri, + 'etc/module.xml' ); - const moduleFile = Uri.joinPath(moduleDirectory, 'etc', 'module.xml'); - return new GeneratedFile(moduleFile, xmlContent); } protected getXmlContent(): string { - const moduleName = this.data.vendor + '_' + this.data.module; + const moduleName = Magento.getModuleName(this.data.vendor, this.data.module); const xml = { '?xml': { '@_version': '1.0', diff --git a/src/generator/observer/ObserverClassGenerator.ts b/src/generator/observer/ObserverClassGenerator.ts index dcdc2e1..219186e 100644 --- a/src/generator/observer/ObserverClassGenerator.ts +++ b/src/generator/observer/ObserverClassGenerator.ts @@ -1,12 +1,13 @@ import FileHeader from 'common/php/FileHeader'; import PhpNamespace from 'common/PhpNamespace'; import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; +import FileGenerator from 'generator/FileGenerator'; import { PhpFile, PsrPrinter } from 'node-php-generator'; import { Uri } from 'vscode'; import { ObserverWizardData } from 'wizard/ObserverWizard'; +import Magento from 'util/Magento'; -export default class ObserverClassGenerator extends ModuleFileGenerator { +export default class ObserverClassGenerator extends FileGenerator { private static readonly OBSERVER_INTERFACE = 'Magento\\Framework\\Event\\ObserverInterface'; private static readonly OBSERVER_CLASS = 'Magento\\Framework\\Event\\Observer'; @@ -17,7 +18,7 @@ export default class ObserverClassGenerator extends ModuleFileGenerator { public async generate(workspaceUri: Uri): Promise { const [vendor, module] = this.data.module.split('_'); const namespaceParts = [vendor, module, this.data.directoryPath]; - const moduleDirectory = this.getModuleDirectory(vendor, module, workspaceUri); + const moduleDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri); const phpFile = new PhpFile(); phpFile.setStrictTypes(true); diff --git a/src/generator/observer/ObserverEventsGenerator.ts b/src/generator/observer/ObserverEventsGenerator.ts index 97420cf..521821c 100644 --- a/src/generator/observer/ObserverEventsGenerator.ts +++ b/src/generator/observer/ObserverEventsGenerator.ts @@ -1,25 +1,23 @@ import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; +import FileGenerator from 'generator/FileGenerator'; import GenerateFromTemplate from 'generator/util/GenerateFromTemplate'; import { Uri } from 'vscode'; import { ObserverWizardData } from 'wizard/ObserverWizard'; import indentString from 'indent-string'; import PhpNamespace from 'common/PhpNamespace'; import FindOrCreateEventsXml from 'generator/util/FindOrCreateEventsXml'; -import { MagentoScope } from 'types'; +import Magento from 'util/Magento'; -export default class ObserverDiGenerator extends ModuleFileGenerator { +export default class ObserverDiGenerator extends FileGenerator { public constructor(protected data: ObserverWizardData) { super(); } public async generate(workspaceUri: Uri): Promise { const [vendor, module] = this.data.module.split('_'); - const moduleDirectory = this.getModuleDirectory(vendor, module, workspaceUri); + const etcDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri, 'etc'); const observerNamespace = PhpNamespace.fromParts([vendor, module, this.data.directoryPath]); - const areaPath = this.data.area === MagentoScope.Global ? '' : this.data.area; - - const eventsFile = Uri.joinPath(moduleDirectory, 'etc', areaPath, 'events.xml'); + const eventsFile = Magento.getUriWithArea(etcDirectory, 'events.xml', this.data.area); const eventsXml = await FindOrCreateEventsXml.execute( workspaceUri, vendor, diff --git a/src/generator/plugin/PluginClassGenerator.ts b/src/generator/plugin/PluginClassGenerator.ts index bf57fe4..304eda0 100644 --- a/src/generator/plugin/PluginClassGenerator.ts +++ b/src/generator/plugin/PluginClassGenerator.ts @@ -1,6 +1,5 @@ import FileHeader from 'common/php/FileHeader'; import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; import { upperFirst } from 'lodash-es'; import { PhpFile, PsrPrinter } from 'node-php-generator'; import { PhpClass } from 'parser/php/PhpClass'; @@ -8,8 +7,10 @@ import { PhpInterface } from 'parser/php/PhpInterface'; import { PhpMethod } from 'parser/php/PhpMethod'; import { Uri } from 'vscode'; import { PluginContextWizardData } from 'wizard/PluginContextWizard'; +import FileGenerator from 'generator/FileGenerator'; +import Magento from 'util/Magento'; -export default class PluginClassGenerator extends ModuleFileGenerator { +export default class PluginClassGenerator extends FileGenerator { public constructor( protected data: PluginContextWizardData, protected subjectClass: PhpClass | PhpInterface, @@ -22,8 +23,9 @@ export default class PluginClassGenerator extends ModuleFileGenerator { const [vendor, module] = this.data.module.split('_'); const nameParts = this.data.className.split(/[\\/]+/); const pluginName = nameParts.pop() as string; + const parts = [vendor, module, 'Plugin', ...nameParts]; - const moduleDirectory = this.getModuleDirectory(vendor, module, workspaceUri); + const moduleDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri); const pluginMethodName = `${this.data.type}${upperFirst(this.data.method)}`; diff --git a/src/generator/plugin/PluginDiGenerator.ts b/src/generator/plugin/PluginDiGenerator.ts index 49eac25..e109da3 100644 --- a/src/generator/plugin/PluginDiGenerator.ts +++ b/src/generator/plugin/PluginDiGenerator.ts @@ -1,5 +1,5 @@ import GeneratedFile from 'generator/GeneratedFile'; -import ModuleFileGenerator from 'generator/ModuleFileGenerator'; +import FileGenerator from 'generator/FileGenerator'; import FindOrCreateDiXml from 'generator/util/FindOrCreateDiXml'; import GenerateFromTemplate from 'generator/util/GenerateFromTemplate'; import { PhpClass } from 'parser/php/PhpClass'; @@ -8,8 +8,9 @@ import { Uri } from 'vscode'; import { PluginContextWizardData } from 'wizard/PluginContextWizard'; import indentString from 'indent-string'; import { PhpInterface } from 'parser/php/PhpInterface'; +import Magento from 'util/Magento'; -export default class PluginDiGenerator extends ModuleFileGenerator { +export default class PluginDiGenerator extends FileGenerator { public constructor( protected data: PluginContextWizardData, protected subjectClass: PhpClass | PhpInterface, @@ -20,16 +21,19 @@ export default class PluginDiGenerator extends ModuleFileGenerator { public async generate(workspaceUri: Uri): Promise { const [vendor, module] = this.data.module.split('_'); - const moduleDirectory = this.getModuleDirectory(vendor, module, workspaceUri); + const etcDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri, 'etc'); + const diFile = Magento.getUriWithArea(etcDirectory, 'di.xml', this.data.scope); const subjectNamespace = this.subjectClass.namespace + '\\' + this.subjectClass.name; - const pluginType = this.getModuleNamespace(vendor, module) + '\\Plugin\\' + this.data.className; - const diFile = Uri.joinPath(moduleDirectory, 'etc', 'di.xml'); - const diXml = await FindOrCreateDiXml.execute(workspaceUri, vendor, module); + const pluginType = Magento.getModuleNamespace(vendor, module).append( + 'Plugin', + this.data.className + ); + const diXml = await FindOrCreateDiXml.execute(workspaceUri, vendor, module, this.data.scope); const insertPosition = this.getInsertPosition(diXml); const pluginXml = await GenerateFromTemplate.generate('xml/plugin', { pluginName: this.data.name, - pluginType, + pluginType: pluginType.toString(), sortOrder: this.data.sortOrder, subjectNamespace, }); diff --git a/src/generator/preference/PreferenceClassGenerator.ts b/src/generator/preference/PreferenceClassGenerator.ts new file mode 100644 index 0000000..a8780ef --- /dev/null +++ b/src/generator/preference/PreferenceClassGenerator.ts @@ -0,0 +1,42 @@ +import FileHeader from 'common/php/FileHeader'; +import GeneratedFile from 'generator/GeneratedFile'; +import { PhpFile, PsrPrinter } from 'node-php-generator'; +import { Uri } from 'vscode'; +import { PreferenceWizardData } from 'wizard/PreferenceWizard'; +import FileGenerator from 'generator/FileGenerator'; +import Magento from 'util/Magento'; + +export default class PreferenceClassGenerator extends FileGenerator { + public constructor(protected data: PreferenceWizardData) { + super(); + } + + public async generate(workspaceUri: Uri): Promise { + const [vendor, module] = this.data.module.split('_'); + const namespaceParts = [vendor, module, this.data.directory]; + const moduleDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri); + + const header = FileHeader.getHeader(this.data.module); + + const phpFile = new PhpFile(); + if (header) { + phpFile.addComment(header); + } + phpFile.setStrictTypes(true); + + const namespace = phpFile.addNamespace(namespaceParts.join('\\')); + + const preferenceClass = namespace.addClass(this.data.className); + + if (this.data.inheritClass && this.data.parentClass) { + preferenceClass.setExtends(this.data.parentClass); + } + + const printer = new PsrPrinter(); + + return new GeneratedFile( + Uri.joinPath(moduleDirectory, this.data.directory, `${this.data.className}.php`), + printer.printFile(phpFile) + ); + } +} diff --git a/src/generator/preference/PreferenceDiGenerator.ts b/src/generator/preference/PreferenceDiGenerator.ts new file mode 100644 index 0000000..12f03f7 --- /dev/null +++ b/src/generator/preference/PreferenceDiGenerator.ts @@ -0,0 +1,40 @@ +import GeneratedFile from 'generator/GeneratedFile'; +import FindOrCreateDiXml from 'generator/util/FindOrCreateDiXml'; +import GenerateFromTemplate from 'generator/util/GenerateFromTemplate'; +import { Uri } from 'vscode'; +import { PreferenceWizardData } from 'wizard/PreferenceWizard'; +import indentString from 'indent-string'; +import Magento from 'util/Magento'; +import FileGenerator from 'generator/FileGenerator'; + +export default class PreferenceDiGenerator extends FileGenerator { + public constructor(protected data: PreferenceWizardData) { + super(); + } + + public async generate(workspaceUri: Uri): Promise { + const [vendor, module] = this.data.module.split('_'); + const etcDirectory = Magento.getModuleDirectory(vendor, module, workspaceUri, 'etc'); + const diFile = Magento.getUriWithArea(etcDirectory, 'di.xml', this.data.area); + const diXml = await FindOrCreateDiXml.execute(workspaceUri, vendor, module, this.data.area); + const insertPosition = this.getInsertPosition(diXml); + + const pluginXml = await GenerateFromTemplate.generate('xml/preference', { + forClass: this.data.parentClass, + typeClass: this.data.className, + }); + + const newDiXml = + diXml.slice(0, insertPosition) + + '\n' + + indentString(pluginXml, 4) + + '\n' + + diXml.slice(insertPosition); + + return new GeneratedFile(diFile, newDiXml, false); + } + + private getInsertPosition(diXml: string): number { + return diXml.indexOf(''); + } +} diff --git a/src/generator/util/FindOrCreateDiXml.ts b/src/generator/util/FindOrCreateDiXml.ts index c256771..6e16a1a 100644 --- a/src/generator/util/FindOrCreateDiXml.ts +++ b/src/generator/util/FindOrCreateDiXml.ts @@ -2,10 +2,18 @@ import { Uri } from 'vscode'; import FileSystem from 'util/FileSystem'; import GenerateFromTemplate from './GenerateFromTemplate'; import FileHeader from 'common/xml/FileHeader'; +import { MagentoScope } from 'types'; +import Magento from 'util/Magento'; export default class FindOrCreateDiXml { - public static async execute(workspaceUri: Uri, vendor: string, module: string): Promise { - const diFile = Uri.joinPath(workspaceUri, 'app', 'code', vendor, module, 'etc', 'di.xml'); + public static async execute( + workspaceUri: Uri, + vendor: string, + module: string, + area: MagentoScope = MagentoScope.Global + ): Promise { + const modulePath = Magento.getModuleDirectory(vendor, module, workspaceUri, 'etc'); + const diFile = Magento.getUriWithArea(modulePath, 'di.xml', area); if (await FileSystem.fileExists(diFile)) { return await FileSystem.readFile(diFile); diff --git a/src/util/Magento.ts b/src/util/Magento.ts index b56baed..790944c 100644 --- a/src/util/Magento.ts +++ b/src/util/Magento.ts @@ -1,4 +1,7 @@ +import PhpNamespace from 'common/PhpNamespace'; import lowerFirst from 'lodash-es/lowerFirst'; +import { MagentoScope } from 'types'; +import { Uri } from 'vscode'; export default class Magento { public static isPluginMethod(method: string) { @@ -8,4 +11,33 @@ export default class Magento { public static pluginMethodToMethodName(method: string): string | undefined { return lowerFirst(method.replace(/^around|^before|^after/, '')); } + + public static getUriWithArea( + baseUri: Uri, + filePath: string, + area: MagentoScope = MagentoScope.Global + ) { + if (area === MagentoScope.Global) { + return Uri.joinPath(baseUri, filePath); + } + + return Uri.joinPath(baseUri, area, filePath); + } + + public static getModuleDirectory( + vendor: string, + module: string, + baseUri: Uri, + path: string = '' + ): Uri { + return Uri.joinPath(baseUri, 'app', 'code', vendor, module, path); + } + + public static getModuleName(vendor: string, module: string): string { + return `${vendor}_${module}`; + } + + public static getModuleNamespace(vendor: string, module: string): PhpNamespace { + return PhpNamespace.fromParts([vendor, module]); + } } diff --git a/src/wizard/PreferenceWizard.ts b/src/wizard/PreferenceWizard.ts new file mode 100644 index 0000000..f001dd8 --- /dev/null +++ b/src/wizard/PreferenceWizard.ts @@ -0,0 +1,83 @@ +import IndexManager from 'indexer/IndexManager'; +import ModuleIndexer from 'indexer/module/ModuleIndexer'; +import { MagentoScope } from 'types'; +import { GeneratorWizard } from 'webview/GeneratorWizard'; +import { WizardFieldBuilder } from 'webview/WizardFieldBuilder'; +import { WizardFormBuilder } from 'webview/WizardFormBuilder'; +import { WizardTabBuilder } from 'webview/WizardTabBuilder'; + +export interface PreferenceWizardData { + module: string; + parentClass: string; + area: MagentoScope; + className: string; + directory: string; + inheritClass: boolean; +} + +export default class PreferenceWizard extends GeneratorWizard { + public async show(parentClass?: string, contextModule?: string): Promise { + const moduleIndexData = IndexManager.getIndexData(ModuleIndexer.KEY); + + if (!moduleIndexData) { + throw new Error('Module index data not found'); + } + + const modules = moduleIndexData.getModuleOptions(m => m.location === 'app'); + + const builder = new WizardFormBuilder(); + + builder.setTitle('Generate a new preference'); + builder.setDescription('Generates a new Magento2 preference class.'); + + const tab = new WizardTabBuilder(); + tab.setId('preference'); + tab.setTitle('Preference'); + + tab.addField( + WizardFieldBuilder.select('module', 'Module*') + .setOptions(modules) + .setInitialValue(contextModule || modules[0].value) + .build() + ); + + tab.addField( + WizardFieldBuilder.select('area', 'Area*') + .setOptions(Object.values(MagentoScope).map(a => ({ label: a, value: a }))) + .setInitialValue(MagentoScope.Global) + .build() + ); + + tab.addField( + WizardFieldBuilder.text('parentClass', 'Parent Class*') + .setInitialValue(parentClass) + .setPlaceholder('Parent class') + .build() + ); + + tab.addField( + WizardFieldBuilder.text('className', 'Class Name*').setPlaceholder('Class name').build() + ); + + tab.addField( + WizardFieldBuilder.text('directory', 'Directory*') + .setInitialValue('Model') + .setPlaceholder('Class directory') + .build() + ); + + tab.addField(WizardFieldBuilder.checkbox('inheritClass', 'Inherit Class').build()); + + builder.addTab(tab.build()); + + builder.addValidation('module', 'required'); + builder.addValidation('area', 'required'); + builder.addValidation('className', 'required|min:1'); + builder.addValidation('parentClass', 'required|min:1'); + builder.addValidation('directory', 'required|min:1'); + + const data = await this.openWizard(builder.build()); + + return data; + } +} diff --git a/templates/xml/preference.ejs b/templates/xml/preference.ejs new file mode 100644 index 0000000..183dc4f --- /dev/null +++ b/templates/xml/preference.ejs @@ -0,0 +1 @@ + \ No newline at end of file