Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

Commit

Permalink
[resolver] use webpack-resolver for core logic (#15)
Browse files Browse the repository at this point in the history
* [resolver] use webpack-resolver for core logic

* use better name for `fromRoot()` function

* [resolver] add default aliases from test_bundle

* remove trailing whitespace

* enabled node eslint-env

* always look in kibana plugins

also observe the kibanaPath

* add eslint-plugin-import

* replace find-root with pkg-up

* add options info to readme

* use pluginName config, if provided

recurse up paths until a package.json file with a matching name is found, if pluginName is provided. throw if none is found

* break index into multiple module files

* add debugging info to readme

* switch pluginName to rootPackageName

* switch pluginName to rootPackageName

* rename rootPath to projectRoot

* remove pkg-up dependency

* move resolution caching back to index
  • Loading branch information
spalger authored Jun 1, 2017
1 parent c11e1f8 commit 4fa60e7
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 233 deletions.
43 changes: 20 additions & 23 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
module.exports = {
"env": {
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
env: {
commonjs: true,
es6: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
],
plugins: [
'import',
],
rules: {
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'import/no-unresolved': [2, { commonjs: true, amd: true }]
}
};
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,19 @@ settings:
kibana: { kibanaPath: '/path/to/kibana' }
```

See [the resolvers docs](https://github.com/benmosher/eslint-plugin-import#resolvers) or the [resolver spec](https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/README.md#resolvesource-file-config---found-boolean-path-string-) for more details.
See [the resolvers docs](https://github.com/benmosher/eslint-plugin-import#resolvers) or the [resolver spec](https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/README.md#resolvesource-file-config---found-boolean-path-string-) for more details.

## Configuration

Property | Default | Descritpion
-------- | ------- | -----------
kibanaPath | `../kibana` | Relative path to the kibana root
rootPackageName | | The `name` property in the root `package.json` file, required when your plugin has multiple plugins with their own`package.json` files
pluginDirs | `[]` | Array of additional directories to check for Kibana plugins
pluginPaths | `[]` | Array of additional paths to look in when resolving plugin dependencies

## Debugging

For debugging output from this resolver, run your linter with `DEBUG=eslint-plugin-import:resolver:kibana`.

This resolver makes heavy use of *eslint-import-resolver-webpack*, and you can get debugging output from both resolver by using `DEBUG=eslint-plugin-import:resolver:*`.
217 changes: 11 additions & 206 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,213 +1,18 @@
const path = require('path');
const findRoot = require('find-root');
const glob = require('glob');
const debug = require('debug')('eslint-import-resolver-kibana');
const webpackResolver = require('eslint-import-resolver-webpack');
const getProjectRoot = require('./lib/get_project_root');
const getWebpackConfig = require('./lib/get_webpack_config');

const defaults = {
kibanaPath: '../kibana',
};

/*
* Resolves the path to Kibana, either from default setting or config
*/
function getKibanaPath(config, file, rootPath) {
const inConfig = config != null && config.kibanaPath;

const kibanaPath = (inConfig)
? path.resolve(config.kibanaPath)
: path.resolve(rootPath, defaults.kibanaPath);

debug(`resolved kibana path: ${kibanaPath}`);
return kibanaPath;
}

/*
* Creates a glob pattern string that looks for:
* - file that matches the source (source.js)
* - directory with an index.js that matches the source (source/index.js)
* - directory with a matching module name that matches the source (source/source.js)
* NOTE: last condition mentioned above can be removed when the custom resolver is removed from Kibana webpack config
* @param {String|Array} source: the module identifier (./imported-file).
*/
function getGlobPattern(source) {
if (Array.isArray(source)) {
const rootPath = path.join(...source);
const filename = source[source.length - 1];
return `./${rootPath}{*(.js),/${filename}.js,/index.js}`;
} else {
return `./${source}{*(.js),/${source}.js,/index.js}`;
}
}

/*
* Returns an array of relative file path strings that match the source
* @param {String} source: the module identifier
* @param {String} checkPath: path to search in for file globbing
*/
function getFileMatches(source, checkPath) {
const globPattern = getGlobPattern(source);
const globOptions = {
cwd: path.resolve(checkPath),
};

const matches = glob.sync(globPattern, globOptions);
debug(`checking in ${checkPath}, matched ${matches.length}`);

return matches;
}

/*
* Return an object with a found property of `true` or `false`
* If found, returns a `path` property of the matched file
* @param {Array} matches: relative paths to check
* @param {String} checkPath: prefix for the path
*/
function getMatch(matches, checkPath) {
if (Array.isArray(matches) && matches.length >= 1) {
const matchPath = path.resolve(checkPath, matches[matches.length - 1]);
debug(`matched path: ${matchPath}`);
return {
found: true,
path: matchPath,
};
}

return { found: false };
}

/*
* Resolves imports in local plugin that begin with `plugin/`
* NOTE: this does not resolve across different plugins
* NOTE: when webpack aliases are removed from Kibana, this will no longer be needed.
*/
function resolvePluginsAliasImport(pluginsImport, kibanaPath, rootPath) {
const { name: packageName } = require(path.resolve(rootPath, 'package.json'));
const [ pluginName, ...importPaths ] = pluginsImport[1].split('/');
debug(`resolvePluginsAliasImport: package ${packageName}, plugin ${pluginName}, import ${importPaths.join('/')}`);

if (packageName !== 'kibana' && packageName === pluginName) {
// resolve local plugin path
const checkPath = path.join(rootPath, 'public');
const matches = getFileMatches(importPaths, checkPath);
return getMatch(matches, checkPath);
} else {
// resolve kibana core plugin path
const checkPath = path.join(kibanaPath, 'src', 'core_plugins', pluginName, 'public');
const matches = getFileMatches(importPaths, checkPath);
return getMatch(matches, checkPath);
}
}
// cache expensive resolution results
let projectRoot;
let webpackConfig;

/*
* Resolves imports where source is a directory with relative path and has a source file with the same name
* @param {Array} fileImport: source of relative
* @param {String} file: absolute path to the file making the import
* @param {String} rootPath: root path of the project code
*/
function resolveLocalRelativeImport(fileImport, file) {
const sourceBase = path.basename(fileImport);
const localPath = path.dirname(path.resolve(path.dirname(file), fileImport));
debug(`resolving relative path: ${localPath}`);
const matches = getFileMatches(sourceBase, localPath);
return getMatch(matches, localPath);
}

/*
* Attempts to resolve imports as webpackShims, either in Kibana or in the local plugin
* @param {String} source: the module identifier
* @param {String} kibanaPath: path to Kibana, default or configured
* @param {String} rootPath: root path of the project code
*/
function resolveWebpackShim(source, kibanaPath, rootPath) {
const pluginShimPath = path.join(rootPath, 'webpackShims');
const pluginMatches = getFileMatches(source, pluginShimPath);
const pluginFileMatches = getMatch(pluginMatches, pluginShimPath);
debug(`resolveWebpackShim: checking for ${source}`);
if (pluginFileMatches.found) {
debug(`resolved webpackShim import in plugin: ${source}`);
return pluginFileMatches;
}

const kibanaShimPath = path.join(kibanaPath, 'webpackShims');
const kibanaMatches = getFileMatches(source, kibanaShimPath);
const kibanaFileMatches = getMatch(kibanaMatches, kibanaShimPath);
if (kibanaFileMatches.found) {
debug(`resolved webpackShim import in Kibana: ${source}`);
}
return kibanaFileMatches;
}

/*
* Resolves global import aliases that map a prefix to a Kibana source directory
* Used for UI: https://github.com/elastic/kibana/blob/5c04ff65fbb3b16f8958f8241488463136415670/src/ui/ui_bundler_env.js#L29
* Used for tests: https://github.com/elastic/kibana/blob/5c04ff65fbb3b16f8958f8241488463136415670/src/core_plugins/tests_bundle/index.js#L70-L75
*/
function resolveKibanaModuleImport(source, kibanaPath) {
debug(`resolveKibanaModuleImport: checking ${kibanaPath} for ${source}`);
const checkPaths = [
path.join(kibanaPath), // ui_framework/components
path.join(kibanaPath, 'src', 'core_plugins', 'dev_mode', 'public'), // ng_mock
path.join(kibanaPath, 'src', 'fixtures'), // fixtures/something
path.join(kibanaPath, 'src', 'test_harness', 'public'), // test_harness/something
path.join(kibanaPath, 'src', 'test_utils', 'public'), // test_utils/something
path.join(kibanaPath, 'src', 'ui', 'public'), // ui/something
];

// clean the source
// strip off leading prefix, if its there (ui, fixtures, test_utils, test_harness)
const baseSource = source.replace(/^(ui|fixtures|test_utils|test_harness)\//, '');
let resolved = { found: false };
checkPaths.forEach(function (checkPath) {
if (!resolved.found) {
debug(`resolveKibanaModuleImport: check for ${baseSource} in ${checkPath}`);
const matches = getFileMatches(baseSource, checkPath);
const match = getMatch(matches, checkPath);
if (match.found) {
debug(`resolving ui import ${source} as ${match.path}`);
resolved = match;
}
}
});
return resolved;
}

function stripPath(strip, result) {
if (!result.found || !strip) return result;
return {
found: result.found,
path: null,
};
}

/*
* See
* https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/README.md#resolvesource-file-config---found-boolean-path-string-
* @param {String} source: the module identifier (./imported-file).
* @param {String} file: the absolute path to the file making the import (/some/path/to/module.js)
* @param {Object} config: an object provided via the import/resolver setting.
*/
exports.resolve = function resolveKibanaPath(source, file, config) {
const rootPath = findRoot(file);
const kibanaPath = getKibanaPath(config, file, rootPath);

const loaderPrefix = source.match(/\!*(raw|file.+)\!+(.*)/);
const loaderPrefixed = (loaderPrefix !== null);
const realSource = (loaderPrefixed) ? loaderPrefix[2] : source;
const pathFix = (result) => stripPath(loaderPrefixed, result);

// check relative paths
const relativeImport = Boolean(realSource.match(new RegExp('^\\.\\.?/(.*)')));
if (relativeImport) return pathFix(resolveLocalRelativeImport(realSource, file));
projectRoot = projectRoot || getProjectRoot(file, config);
webpackConfig = webpackConfig || getWebpackConfig(source, projectRoot, config);

// check local plugins path (resolves kibana webpack alias)
const pluginsImport = realSource.match(new RegExp('^plugins/(.*)'));
if (pluginsImport !== null) return pathFix(resolvePluginsAliasImport(pluginsImport, kibanaPath, rootPath));

// check for matches in kibana (using kibana webpack aliases)
const aliasModuleImport = pathFix(resolveKibanaModuleImport(realSource, kibanaPath));
if (aliasModuleImport.found) return aliasModuleImport;

return resolveWebpackShim(realSource, kibanaPath, rootPath);
return webpackResolver.resolve(source, file, {
config: webpackConfig
});
};

// use version 2 of the resolver interface, https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/README.md#interfaceversion--number
Expand Down
3 changes: 3 additions & 0 deletions lib/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const debug = require('debug')('eslint-plugin-import:resolver:kibana');

module.exports = debug;
5 changes: 5 additions & 0 deletions lib/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
kibanaPath: '../kibana',
pluginDirs: [],
pluginPaths: [],
};
21 changes: 21 additions & 0 deletions lib/get_kibana_path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { resolve } = require('path');
const debug = require('./debug');
const defaults = require('./defaults');

let kibanaPath;

/*
* Resolves the path to Kibana, either from default setting or config
*/
module.exports = function getKibanaPath(config, projectRoot) {
if (kibanaPath) return kibanaPath;

const inConfig = config != null && config.kibanaPath;

kibanaPath = (inConfig)
? resolve(config.kibanaPath)
: resolve(projectRoot, defaults.kibanaPath);

debug(`Resolved Kibana path: ${kibanaPath}`);
return kibanaPath;
};
28 changes: 28 additions & 0 deletions lib/get_plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { dirname, resolve } = require('path');
const glob = require('glob-all');

const defaults = require('./defaults');

module.exports = function getPlugins(config, kibanaPath, projectRoot) {
const pluginDirs = [
...(config.pluginDirs || defaults.pluginDirs),
resolve(kibanaPath, 'plugins'),
resolve(kibanaPath, 'src', 'core_plugins'),
];

const globPatterns = [
...pluginDirs.map(dir => `${dir}/*/package.json`),
...(config.pluginPaths || defaults.pluginPaths).map(path => `${path}/package.json`),
];
const globOptions = { cwd: projectRoot };

return glob.sync(globPatterns, globOptions).map(pkgJsonPath => {
const path = dirname(pkgJsonPath);
const pkg = require(pkgJsonPath);
return {
name: pkg.name,
directory: path,
publicDirectory: resolve(path, 'public'),
};
});
};
36 changes: 36 additions & 0 deletions lib/get_project_root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const { dirname, resolve, parse } = require('path');
const { accessSync, readFileSync } = require('fs');
const debug = require('./debug');

function getRootPackageDir(dirRoot, dir, rootPackageName) {
if (dirRoot === dir) return null;
const pkgFile = resolve(dir, 'package.json');

try {
accessSync(pkgFile);

// if rootPackageName is not provided, stop when package.json is found
if (!rootPackageName) return dir;

// if rootPackageName is provided, check for match
const { name } = JSON.parse(readFileSync(pkgFile));
if (name === rootPackageName) return dir;

// recurse until a matching package.json is found
return getRootPackageDir(dirRoot, dirname(dir), rootPackageName);
} catch (e) {
if (e.code === 'ENOENT') return getRootPackageDir(dirRoot, dirname(dir), rootPackageName);
throw e;
}
}

module.exports = function getProjectRoot(file, config) {
const { root, dir } = parse(resolve(file));
const { rootPackageName } = config;

projectRoot = getRootPackageDir(root, dir, rootPackageName);
if (projectRoot === null) throw new Error('Failed to find plugin root');

debug(`Resolved project root: ${projectRoot}`);
return projectRoot;
};
Loading

0 comments on commit 4fa60e7

Please sign in to comment.