Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 14 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import nodePlugin from 'eslint-plugin-n';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import pluginJs from '@eslint/js';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const noBarrelImports = require('./eslint/rules/no-barrel-imports.js');

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -41,6 +45,11 @@ export default [
plugins: {
'ember-internal': emberInternal,
'disable-features': disableFeatures,
local: {
rules: {
'no-barrel-imports': noBarrelImports,
},
},
},

linterOptions: {
Expand Down Expand Up @@ -78,6 +87,11 @@ export default [
'disable-features/disable-generator-functions': 'error',
// Doesn't work with package.json#exports
'import/no-unresolved': 'off',

// Prevent importing from barrel/entrypoint files in internal packages.
// Source files should import directly from the specific lib/ file to
// enable proper tree-shaking.
'local/no-barrel-imports': 'error',
},
},
...tseslint.configs.recommended.map((config) => ({
Expand Down
79 changes: 79 additions & 0 deletions eslint/rules/no-barrel-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* ESLint rule: no-barrel-imports
*
* Disallows importing from barrel/entrypoint files of internal packages.
* Internal source files should import directly from the specific lib/ file
* to enable proper tree-shaking by bundlers.
*
* For example:
* Bad: import { Renderer } from '@ember/-internals/glimmer';
* Good: import { Renderer } from '@ember/-internals/glimmer/lib/renderer';
*/

'use strict';

// Barrel packages that should not be imported directly from source files.
// Each entry maps a bare package specifier to a human-readable hint.
const BARREL_PACKAGES = new Map([
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just all of them. and an autofixer should read in the import from the barrel file, and get its real path. an error should still occur if the imported thing is a re-export

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it — will expand to all barrel imports with auto-fix that traces re-exports to their source files.

[
'@ember/-internals/glimmer',
"Import from '@ember/-internals/glimmer/lib/...' instead of the barrel '@ember/-internals/glimmer'.",
],
[
'@ember/-internals/environment',
"Import from '@ember/-internals/environment/lib/env' or '@ember/-internals/environment/lib/context' instead of the barrel '@ember/-internals/environment'.",
],
]);

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {

Check failure on line 29 in eslint/rules/no-barrel-imports.js

View workflow job for this annotation

GitHub Actions / tests / Linting

'module' is not defined
meta: {
type: 'suggestion',
docs: {
description:
'Disallow imports from barrel/entrypoint files; require direct file imports instead',
},
messages: {
noBarrelImport:
"Do not import from the barrel '{{source}}'. {{hint}}",
},
schema: [], // no options
},

create(context) {
// Only apply to files inside packages/ that are NOT test or type-test files.
const filename = context.filename || context.getFilename();

// Skip files outside packages/
if (!filename.includes('/packages/')) {
return {};
}

// Skip test and type-test files
if (/\/(tests|type-tests)\//.test(filename)) {
return {};
}

function check(node) {
const source = node.source && node.source.value;
if (typeof source !== 'string') return;

const hint = BARREL_PACKAGES.get(source);
if (hint) {
context.report({
node: node.source,
messageId: 'noBarrelImport',
data: { source, hint },
});
}
}

return {
ImportDeclaration: check,
// Also catch: export { foo } from '@ember/-internals/glimmer';
ExportNamedDeclaration: check,
// Also catch: export * from '@ember/-internals/glimmer';
ExportAllDeclaration: check,
};
},
};
Loading