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

[resolver] use webpack-resolver for core logic #15

Merged
merged 18 commits into from
Jun 1, 2017
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
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