diff --git a/README.md b/README.md index ba401a9..276d2c1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The main idea of this extension is to automatically pick your alias configuratio This extension is heavily inspired on webstorm's behavior, it reads the webpack configuration, and resolve the aliases based on this. -What if you don't use webpack? Well, this extension lets you define which file you want it to read. So you could use rollup for instance. +What if you don't use webpack? Well, this extension lets you define which file you want it to read and you can also customize the way it access to the object. ## Features @@ -27,3 +27,39 @@ It is also a must that vscode autocompletes this kind of routes when we are typi This extension contributes the following settings: - `alias-resolver.file`: File where your alias definition is. +- `alias-resolver.type`: The type of config you will be using. [webpack | rollup | custom] +- `alias-resolve.accessPath`: This settings lets you define where do you aliases leave. For instance, if you export an object from your file with a shape like this: + +```js +module.exports = { + a: { + b: { + alias: { + '@': path.resolve(__dirname, 'src/') + } + } + } +} +``` + +This setting would be `a.b.alias`. If you are exporting an object with aliases directly on the root, then you can ignore this setting, or leave it as an empty string. + +## Extension commands + +```json + { + "command": "alias-resolver.updateConfigFile", + "title": "Update configuration file", + "category": "Alias resolver" + }, + { + "command": "alias-resolver.updateConfigType", + "title": "Update configuration type", + "category": "Alias resolver" + }, + { + "command": "alias-resolver.updateConfigAccessType", + "title": "Update configuration access path to object", + "category": "Alias resolver" + } +``` \ No newline at end of file diff --git a/extension.js b/extension.js index 38e1ffc..965512c 100644 --- a/extension.js +++ b/extension.js @@ -2,17 +2,16 @@ const vscode = require('vscode'); const registerCommands = require('./src/commands'); const registerProviders = require('./src/providers'); const { ConfigParser } = require('./src/parser'); -const { getConfig, configureFile } = require('./src/config'); +const userConfig = require('./src/user-config'); const { ACTIONS } = require('./src/constants'); /** * @param {vscode.ExtensionContext} context */ async function activate(context) { - registerCommands(); + registerCommands(userConfig); - const { file } = getConfig(); - let fileName; + let { file, type, accessPath } = userConfig.getConfig(); if (!file) { const prompt = await vscode.window.showInformationMessage( @@ -25,28 +24,47 @@ async function activate(context) { return; } - fileName = await configureFile(); + ({ + file, + type, + accessPath, + } = await userConfig.configureExtensionSettings()); - if (!fileName) { + if (!file) { return; } } - vscode.workspace - .findFiles(fileName || file, '**/node_modules/**') - .then(([{ path }]) => { - vscode.workspace.openTextDocument(path).then((document) => { + // TODO: Refactor how this is handle so there's no repeated code + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration('alias-resolver')) { + const { file, type, accessPath } = userConfig.getConfig(); + + vscode.workspace.findFiles(file, '**/node_modules/**').then((result) => { + const path = result[0].path; + const config = require(path); + vscode.window.showInformationMessage( - `Using ${fileName || file} to resolve alias paths` + `Using ${file} to resolve alias paths` ); - const text = document.getText(); - ConfigParser.createMappingsFromConfig(text); - registerProviders(context); + ConfigParser.createMappingsFromConfig(config, { type, accessPath }); }); - }); + } + }); + + vscode.workspace.findFiles(file, '**/node_modules/**').then((result) => { + const path = result[0].path; + const config = require(path); + + vscode.window.showInformationMessage( + `Using ${file} to resolve alias paths` + ); + + ConfigParser.createMappingsFromConfig(config, { type, accessPath }); + registerProviders(context); + }); } -exports.activate = activate; function deactivate() {} diff --git a/package-lock.json b/package-lock.json index d36651a..eb0231f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6495,6 +6495,11 @@ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", diff --git a/package.json b/package.json index 45604b2..54f4199 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,17 @@ "type": "string", "default": "", "description": "File with alias mappings such as webpack.js or rollup.config.js" + }, + "alias-resolver.type": { + "type": "string", + "default": "webpack", + "enum": ["webpack", "custom"], + "description": "Type of file with alias mappings." + }, + "alias-resolver.accessPath": { + "type": "string", + "default": "resolve.alias", + "description": "Access to the object where the alias are, using string annotation. e.g. 'resolve.alias' or 'a.b[0].d'" } } }, @@ -36,6 +47,16 @@ "command": "alias-resolver.updateConfigFile", "title": "Update configuration file", "category": "Alias resolver" + }, + { + "command": "alias-resolver.updateConfigType", + "title": "Update configuration type", + "category": "Alias resolver" + }, + { + "command": "alias-resolver.updateConfigAccessType", + "title": "Update configuration access path to object", + "category": "Alias resolver" } ] }, @@ -81,6 +102,7 @@ }, "dependencies": { "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" + "acorn-walk": "^7.1.1", + "lodash.get": "^4.4.2" } } diff --git a/src/alias/alias-map.js b/src/alias/alias-map.js index 3a1d1c6..367c514 100644 --- a/src/alias/alias-map.js +++ b/src/alias/alias-map.js @@ -1,3 +1,3 @@ const alias = new Map(); -module.exports = { alias }; +module.exports = alias; diff --git a/src/commands/commands.js b/src/commands/commands.js index f8ba1fd..a8aa772 100644 --- a/src/commands/commands.js +++ b/src/commands/commands.js @@ -1,10 +1,17 @@ const vscode = require('vscode'); -const { configureFile } = require('../config'); -function registerCommands() { +function registerCommands(userConfig) { vscode.commands.registerCommand( 'alias-resolver.updateConfigFile', - configureFile + userConfig.updateConfigFile.bind(userConfig) + ); + vscode.commands.registerCommand( + 'alias-resolver.updateConfigType', + userConfig.updateConfigType.bind(userConfig) + ); + vscode.commands.registerCommand( + 'alias-resolver.updateConfigAccessType', + userConfig.updateConfigAccessPath.bind(userConfig) ); } diff --git a/src/config/config.js b/src/config/config.js deleted file mode 100644 index 0abb45f..0000000 --- a/src/config/config.js +++ /dev/null @@ -1,44 +0,0 @@ -const vscode = require('vscode'); -const path = require('path'); - -function getConfig() { - const { file, path } = vscode.workspace.getConfiguration('alias-resolver'); - - return { - file, - path, - }; -} - -function updateConfig(file) { - const config = vscode.workspace.getConfiguration('alias-resolver'); - - return config.update('file', file, vscode.ConfigurationTarget.Workspace); -} - -async function configureFile() { - const result = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - }); - - if (!result) { - return; - } - - const [selectedFile] = result; - const { path: selectedFilePath } = selectedFile; - - const fileName = path.basename(selectedFilePath); - - await updateConfig(fileName); - - return fileName; -} - -module.exports = { - getConfig, - updateConfig, - configureFile, -}; diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index f390caf..0000000 --- a/src/config/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./config'); diff --git a/src/file-manager/file-manager.js b/src/file-manager/file-manager.js index 68da7e2..ca074e2 100644 --- a/src/file-manager/file-manager.js +++ b/src/file-manager/file-manager.js @@ -1,12 +1,11 @@ -const path = require('path'); const vscode = require('vscode'); +const path = require('path'); const { getNodesInPath, isAliasPath, getImportPath, getAliasFromPath, resolveAliasPath, - isAbsolutePath, } = require('../utils'); const { DEFAULT_FILE } = require('../constants'); const File = require('./file'); @@ -32,7 +31,8 @@ class FileManager { const rootPath = workspace.uri.path; const resolvedAlias = resolveAliasPath(this.importPath); const resolvedPath = this.importPath.replace(alias, resolvedAlias); - return isAbsolutePath(resolvedPath) + + return path.isAbsolute(resolvedPath) ? resolvedPath : path.join(rootPath, resolvedPath); } diff --git a/src/parser/config-parser.js b/src/parser/config-parser.js index 0bac4d9..a9d6f94 100644 --- a/src/parser/config-parser.js +++ b/src/parser/config-parser.js @@ -1,44 +1,45 @@ -const { Parser } = require('acorn'); -const walk = require('acorn-walk'); -const { alias } = require('../alias/alias-map'); +const alias = require('../alias/alias-map'); +const get = require('lodash.get'); + +const TYPES = { + WEBPACK: 'webpack', + ROLLUP: 'rollup', + CUSTOM: 'custom', +}; + +const ACCESS_PATHS = { + [TYPES.WEBPACK]: 'resolve.alias', + [TYPES.CUSTOM]: '', +}; class ConfigParser { - static createMappingsFromConfig(rawText) { - const ast = Parser.parse(rawText); - - walk.simple(ast, { - Property(node) { - if (node.key.name === 'alias') { - node.value.properties.forEach((prop) => { - const key = prop.key.value; - const value = prop.value; - - walk.simple(value, { - Literal(node) { - key && node.value && alias.set(key, node.value); - }, - }); - }); - } - }, - CallExpression(node) { - if (node.callee.name === 'alias') { - node.arguments.forEach((arg) => { - arg.properties.forEach((prop) => { - const key = prop.key.value; - const value = prop.value; - - walk.simple(value, { - Literal(node) { - key && node.value && alias.set(key, node.value); - }, - }); - }); - }); - } - }, - }); + _webpackAdapter(fileConfig) { + return get(fileConfig, ACCESS_PATHS[TYPES.WEBPACK]); + } + + _customConfigAdapter(fileConfig, accessPath = ACCESS_PATHS[TYPES.CUSTOM]) { + return get(fileConfig, accessPath, fileConfig); + } + + createMappingsFromConfig(fileConfig, options = { type: TYPES.WEBPACK }) { + let objWithAlias = {}; + + switch (options.type) { + case TYPES.WEBPACK: + objWithAlias = this._webpackAdapter(fileConfig); + break; + case TYPES.CUSTOM: + objWithAlias = this._customConfigAdapter( + fileConfig, + options.accessPath + ); + break; + } + + for (const [key, path] of Object.entries(objWithAlias)) { + alias.set(key, path); + } } } -module.exports = { ConfigParser }; +module.exports = { ConfigParser: new ConfigParser() }; diff --git a/src/parser/types.js b/src/parser/types.js deleted file mode 100644 index 8ec2aba..0000000 --- a/src/parser/types.js +++ /dev/null @@ -1,8 +0,0 @@ -const EXPRESSION_TYPES = { - CALL: 'CallExpression', - LITERAL: 'Literal', - IDENTIFIER: 'Identifier', - MEMBER: 'MemberExpression', -}; - -module.exports = { EXPRESSION_TYPES }; diff --git a/src/providers/javascript/index.js b/src/providers/javascript/index.js index 8680342..681ff34 100644 --- a/src/providers/javascript/index.js +++ b/src/providers/javascript/index.js @@ -1 +1 @@ -module.exports = { javascriptProvider: require('./provider') }; +module.exports = require('./provider'); diff --git a/src/providers/javascript/provider.js b/src/providers/javascript/provider.js index aca9006..0e8300f 100644 --- a/src/providers/javascript/provider.js +++ b/src/providers/javascript/provider.js @@ -1,14 +1,17 @@ const { provideDefinition } = require('./provide-definition'); const { provideCompletionItems } = require('./provide-completion-items'); -const { alias } = require('../../alias/alias-map'); -const javaScriptProvider = { - selector: { scheme: 'file', language: 'javascript' }, - providers: { - provideCompletionItems, - provideDefinition, - }, - triggerCharacters: ['/', '"', "'", ...alias.keys()], -}; +function getJavascriptProvider(alias) { + const aliasKeys = Array.from(alias.keys()); -module.exports = javaScriptProvider; + return { + selector: { scheme: 'file', language: 'javascript' }, + providers: { + provideCompletionItems, + provideDefinition, + }, + triggerCharacters: ['/', '"', "'", ...aliasKeys], + }; +} + +module.exports = getJavascriptProvider; diff --git a/src/providers/register-providers.js b/src/providers/register-providers.js index 771600e..c8b659a 100644 --- a/src/providers/register-providers.js +++ b/src/providers/register-providers.js @@ -1,7 +1,9 @@ const vscode = require('vscode'); -const { javascriptProvider } = require('./javascript'); +const getJavascriptProvider = require('./javascript'); +const alias = require('../alias/alias-map'); function registerProviders(context) { + const javascriptProvider = getJavascriptProvider(alias); const { provideCompletionItems, provideDefinition, diff --git a/src/user-config/index.js b/src/user-config/index.js new file mode 100644 index 0000000..4a27c3b --- /dev/null +++ b/src/user-config/index.js @@ -0,0 +1 @@ +module.exports = require('./user-config'); diff --git a/src/user-config/user-config.js b/src/user-config/user-config.js new file mode 100644 index 0000000..b21d86c --- /dev/null +++ b/src/user-config/user-config.js @@ -0,0 +1,106 @@ +const vscode = require('vscode'); + +class UserConfig { + // This is needed to keep a fresh new reference to the config. + get _config() { + return vscode.workspace.getConfiguration('alias-resolver'); + } + + getConfig() { + return this._config; + } + + _updateField(field, value) { + return this._config.update( + field, + value, + vscode.ConfigurationTarget.Workspace + ); + } + + updateConfig(file, type, accessPath) { + return Promise.all([ + this._updateField('file', file), + this._updateField('type', type), + this._updateField('accessPath', accessPath), + ]); + } + + async updateConfigFile() { + const file = await this.showDialogToGetFile(); + + this._updateField('file', file); + } + + async updateConfigType() { + const type = await this.showDialogToGetType(); + + this._updateField('type', type); + } + + async updateConfigAccessPath() { + const accessPath = await this.showDialogToGetAccessPath(); + + this._updateField('accessPath', accessPath); + } + + async configureExtensionSettings() { + const file = await this.showDialogToGetFile(); + const type = await this.showDialogToGetType(); + const accessPath = await this.showDialogToGetAccessPath(); + + await this.updateConfig(file, type, accessPath); + + return { file, type, accessPath }; + } + + async showDialogToGetType() { + const typeResult = await vscode.window.showQuickPick([ + { + label: 'webpack', + detail: 'If your config is a typical webpack config.', + }, + { + label: 'custom', + detail: + 'If you are using a custom config and you will define how to access to the object.', + }, + ]); + + return typeResult.label; + } + + async showDialogToGetAccessPath() { + const accessPath = await vscode.window.showInputBox({ + placeHolder: 'resolve.alias', + prompt: + 'The access path to the object where the alias are. Leave empty if aliases are at the root of the object', + }); + + return accessPath; + } + + async showDialogToGetFile() { + const result = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + }); + + if (!result) { + return; + } + + const [selectedFile] = result; + const { path: selectedFilePath } = selectedFile; + const workspaceRootPath = this.getWorkspaceRootPath(); + + return selectedFilePath.replace(workspaceRootPath + '/', ''); + } + + getWorkspaceRootPath() { + return vscode.workspace.workspaceFolders[0].uri.path; + } +} + +module.exports = new UserConfig(); diff --git a/src/utils/path.js b/src/utils/path.js index eba782b..92b6103 100644 --- a/src/utils/path.js +++ b/src/utils/path.js @@ -1,4 +1,4 @@ -const { alias } = require('../alias/alias-map'); +const alias = require('../alias/alias-map'); const { readdir } = require('fs'); const { promisify } = require('util'); @@ -24,7 +24,7 @@ function getAliasFromPath(path) { function sortAliasFromLongerToShorter(aliasKeys) { return [...aliasKeys].sort((a, b) => { if (a.length > b.length) return -1; - if (b.length > a.lenght) return 1; + if (b.length > a.length) return 1; return 0; }); } @@ -46,10 +46,6 @@ function resolveAliasPath(path) { return alias.get(matchingAlias) || ''; } -function isAbsolutePath(path) { - return path.startsWith('/'); -} - async function getNodesInPath(path) { try { return readdirAsync(path); @@ -60,7 +56,6 @@ async function getNodesInPath(path) { module.exports = { isAliasPath, - isAbsolutePath, resolveAliasPath, getNodesInPath, getAliasFromPath,