eslint-plugin: refactor to ES2021 and add no-undeclared-imports rule
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@manypkg/get-packages')} */
|
||||
const manypkg = require('@manypkg/get-packages');
|
||||
|
||||
/**
|
||||
* @typedef ExtendedPackage
|
||||
* @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record<string, string> }}} packageJson
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PackageMap
|
||||
* @type object
|
||||
*
|
||||
* @property {ExtendedPackage} root
|
||||
* @property {ExtendedPackage[]} list
|
||||
* @property {Map<string, ExtendedPackage>} map
|
||||
*/
|
||||
|
||||
// Loads all packages in the monorepo once, and caches the result
|
||||
module.exports = (function () {
|
||||
let result = undefined;
|
||||
let lastLoadAt = undefined;
|
||||
|
||||
/** @returns {PackageMap | undefined} */
|
||||
return function getPackages(/** @type {string} */ dir) {
|
||||
if (result) {
|
||||
// Only cache for 5 seconds, to avoid the need to reload ESLint servers
|
||||
if (Date.now() - lastLoadAt > 5000) {
|
||||
result = undefined;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
const packages = manypkg.getPackagesSync(dir);
|
||||
if (!packages) {
|
||||
return undefined;
|
||||
}
|
||||
result = {
|
||||
map: new Map(packages.packages.map(pkg => [pkg.packageJson.name, pkg])),
|
||||
list: packages.packages,
|
||||
root: packages.root,
|
||||
};
|
||||
lastLoadAt = Date.now();
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
const getPackages = require('./getPackages');
|
||||
|
||||
/**
|
||||
* @typedef LocalImport
|
||||
* @type {object}
|
||||
* @property {'local'} type
|
||||
* @property {string} path
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef InternalImport
|
||||
* @type {object}
|
||||
* @property {'internal'} type
|
||||
* @property {string} path
|
||||
* @property {import('./getPackages').ExtendedPackage} package
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ExternalImport
|
||||
* @type {object}
|
||||
* @property {'external'} type
|
||||
* @property {string} path
|
||||
* @property {string} packageName
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback ImportVisitor
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {LocalImport | InternalImport | ExternalImport} import
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param visitor - Visitor callback
|
||||
* @param {import('eslint').Rule.RuleContext} context
|
||||
* @param {ImportVisitor} visitor
|
||||
*/
|
||||
module.exports = function visitImports(context, visitor) {
|
||||
const packages = getPackages(context.getCwd());
|
||||
if (!packages) {
|
||||
return;
|
||||
}
|
||||
|
||||
function visit(node) {
|
||||
if (!node.source) {
|
||||
return;
|
||||
}
|
||||
const importPath = node.source.value;
|
||||
if (importPath[0] === '.') {
|
||||
return visitor(node, { type: 'local', path: importPath });
|
||||
}
|
||||
|
||||
const pathParts = importPath.split('/');
|
||||
|
||||
// Check for match with plain name, then namespaced name
|
||||
let packageName;
|
||||
let subPath;
|
||||
if (importPath[0] === '@') {
|
||||
packageName = pathParts.slice(0, 2).join('/');
|
||||
subPath = pathParts.slice(2).join('/');
|
||||
} else {
|
||||
packageName = pathParts[0];
|
||||
subPath = pathParts.slice(1).join('/');
|
||||
}
|
||||
const pkg = packages?.map.get(packageName);
|
||||
if (!pkg) {
|
||||
return visitor(node, {
|
||||
type: 'external',
|
||||
path: subPath,
|
||||
packageName: packageName,
|
||||
});
|
||||
}
|
||||
|
||||
return visitor(node, { type: 'internal', path: subPath, package: pkg });
|
||||
}
|
||||
|
||||
return {
|
||||
ImportDeclaration: visit,
|
||||
ExportAllDeclaration: visit,
|
||||
ExportNamedDeclaration: visit,
|
||||
ImportExpression: visit,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user