-
Notifications
You must be signed in to change notification settings - Fork 18
feat(steiger-plugin): add no-wildcard-exports rule and fix tsconfig resolution #215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the great work! You covered nearly all aspects of creating a rule all by yourself :) The last things to do are to packages/steiger-plugin-fsd/src/index.ts
and add it to disabledRules
(we don't want to enable it by default for now not to break people's CI, we will enable it by default in 0.6, with the next round of breaking changes) and also add it to the table of rules in the README (note that the README is copy-pasted between the project root and packages/steiger
, so both files need to be updated).
if (!/\.(js|jsx|ts|tsx)$/.test(file.path)) continue | ||
|
||
// Allow wildcard exports in unsliced layers (shared, app) | ||
if (sourceFile.layerName === 'shared' || sourceFile.layerName === 'app') continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: let's use isSliced
from @feature-sliced/filesystem
to keep this logic centralized
const fileName = basename(file.path) | ||
const isPublicApiFile = /^index\.(js|jsx|ts|tsx)$/.test(fileName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: likewise, let's use isIndex
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also important because we allow index.client.ts
and index.server.ts
as valid Public APIs, and this function takes that into account
if (!isPublicApiFile) continue | ||
|
||
// Parse file content using oxc-parser | ||
const content = (file as FileWithContent).content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why is the type-cast here necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized — we don't include content
as a field on the VFS node, we might need to explicitly read the file contents with readFile
const content = (file as FileWithContent).content | ||
if (!content) continue // Skip if file has no contents | ||
|
||
const parseResult = parseSync(file.path, content) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: rules can be asynchronous, so we can use the async version of the parser
fixes: [ | ||
{ | ||
type: 'modify-file', | ||
path: file.path, | ||
content: | ||
'// Replace with named exports\n// Example: export { ComponentA, ComponentB } from "./components"', | ||
}, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I don't think that's a great autofix — ideally, autofixes should be safe code changes that you can apply and not worry about your code breaking. This will probably break code
suggestion: let's not offer an autofix for this rule until we're able to determine what names are being wildcard-exported (which is a worthwhile improvement as a second iteration of this rule)
|
||
Forbid wildcard exports (`export *`) in public APIs of business logic layers. Named exports and namespace exports (`export * as namespace`) are allowed. | ||
|
||
**Exception:** Wildcard exports are allowed in unsliced layers (`shared` and `app`), as they serve as foundational layers with different architectural purposes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I would actually argue against allowing wildcard exports even in Shared and App. To me, wildcard exports conceal the public API of a group of modules, which really hurts the ability to discover a codebase shallowly, i. e. without digging into every file.
question: why do you think they should be allowed there? And do you agree with the content of the Public API reference page in the FSD docs?
- Unintentionally exposing internal implementation details | ||
- Difficulty in tracking dependencies between modules | ||
- Potential naming conflicts when multiple modules use wildcard exports |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: I would also add the point about hurting shallow discovery (if you agree with it, that is :D)
- Difficulty in tracking dependencies between modules | ||
- Potential naming conflicts when multiple modules use wildcard exports | ||
|
||
Using named exports or namespace exports makes the public API more explicit and easier to maintain in business logic layers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: let's also leave a link to the docs about Public API
"@feature-sliced/filesystem": "^3.0.1", | ||
"fastest-levenshtein": "^1.0.16", | ||
"lodash-es": "^4.17.21", | ||
"oxc-parser": "^0.47.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: kudos for using the oxc-parser :) I was hoping that we would switch to it entirely at some point and drop precinct
because it isn't flexible enough and doesn't extend well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, it supposedly provides a list of exports in a separate list that's easily accessible, can we use that? See "Returns ESM information" in https://www.npmjs.com/package/oxc-parser
function addContentToFiles(folder: Folder): void { | ||
for (const child of folder.children) { | ||
if (child.type === 'file') { | ||
const fileWithContent = child as FileWithContent | ||
if (child.path === joinFromRoot('shared', 'ui', 'index.ts')) { | ||
fileWithContent.content = "export * as positions from './positions'" | ||
} else if (child.path === joinFromRoot('entities', 'user', 'index.ts')) { | ||
fileWithContent.content = "export * as userModel from './model'" | ||
} else { | ||
fileWithContent.content = '' | ||
} | ||
} else if (child.type === 'folder') { | ||
addContentToFiles(child) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I think this conceals the fact that this test will actually not pass during real usage, since the linter engine doesn't include the content
field
suggestion: let's use FS mocking for tests, like it's done in the test file for forbidden-imports
Background
no-wildcard-exports
rule in@feature-sliced/steiger-plugin
export *
in public APIs of business layersexport * as namespace
and wildcard exports inshared/app
index.(js|jsx|ts|tsx);
non-index and test files are ignoredREADME.md
for the rule@tsconfig/node18
todependencies
in@steiger/tsconfig
for proper tsconfig extends resolution