Feature Sliced ESLint Plugin Bug Rules Not Catching Imports With Same Slice Name Across Layers
Hey guys! Let's dive into a tricky bug we've discovered in the Feature Sliced ESLint plugin. This issue prevents the plugin from correctly identifying imports from segments of other layers when those layers share the same slice name. This can lead to some unexpected behavior, especially in larger projects adhering to the Feature Sliced Design (FSD) methodology. So, let's break it down and see how we can fix it!
The Problem: Same Slice, Different Layers
The core issue lies in how the layers-slices
and public-api
rules within the ESLint plugin operate. These rules are designed to enforce the architectural principles of FSD, ensuring that layers interact in a controlled manner, primarily through public APIs. However, the current implementation has a blind spot: it doesn't always catch imports that bypass these public APIs when slices have the same name across different layers.
To illustrate this, consider a scenario where you have a policies
slice in both the pages
layer and the entities
layer. According to FSD, the pages
layer should only import from the entities
layer via its public API. This means accessing modules directly within segments like api
or lib
should be flagged as an error. Unfortunately, this isn't happening in certain cases.
Here’s a real-world example:
// File: src/pages/policies/ui/PolicyNodeDetailsPage.vue
import { getNodePolicyById } from "@/entities/policies/api";
import { createNodePolicyFields } from "@/entities/policies/lib";
import type { PolicyDetailField } from "@/entities/policies/lib";
In this snippet, the PolicyNodeDetailsPage.vue
component, residing in the pages
layer, is directly importing from the api
and lib
segments of the policies
slice within the entities
layer. This violates FSD principles, as it bypasses the intended public API (e.g., @/entities/policies
). Ideally, the ESLint plugin should flag these imports as errors and suggest using the public API instead. But currently, it doesn't.
Why is this happening?
The root cause lies within the isSameSlice
calculation in the src/lib/feature-sliced/extract-paths-info.ts
file. Specifically, the logic at lines 36-37:
const isSameSlice = target.validatedFeatureSlicedParts.hasSlice && currentFile.validatedFeatureSlicedParts.hasSlice
&& target.fsdParts.slice === currentFile.fsdParts.slice;
This code snippet checks if the slice names are identical (policies === policies
). While this is a necessary check, it's insufficient on its own. The critical missing piece is a check to ensure that the slices also belong to the same layer. The current logic doesn't differentiate between a policies
slice in the pages
layer and a policies
slice in the entities
layer. As long as the slice names match, isSameSlice
evaluates to true
, even when the layers are distinct (pages
!== entities
).
When isSameSlice
returns true
incorrectly, it triggers a cascade of issues:
- The
layers-slices
rule skips validation because of theisNotSuitableForValidation
function insrc/rules/layers-slices/model/is-not-suitable-for-validation.ts
(lines 14-16). - Similarly, the
public-api
rule bypasses validation due to theshouldBeFromSlicePublicApi
function insrc/rules/public-api/model/should-be-from-public-api.ts
(lines 19-22).
In essence, the incorrect isSameSlice
calculation effectively disables the intended checks for these imports, allowing architectural violations to slip through the cracks. This can lead to tightly coupled code and make it harder to maintain and evolve your application over time.
Expected Behavior: Catching the Violations
So, what should the ESLint plugin do in these situations? The expected behavior is clear: it should flag these direct imports from internal segments of other layers as violations. For the example above, the plugin should report an error similar to:
@conarti/feature-sliced/public-api
should report: "Absolute imports are only allowed from public api"- Suggesting to use
@/entities/policies
instead
This error message clearly communicates the issue and provides a concrete suggestion for how to fix it. By enforcing the use of public APIs, the plugin helps maintain a clear separation of concerns and ensures that layers interact in a predictable and controlled manner.
The Suggested Fix: Adding a Layer Check
The solution to this bug is relatively straightforward. We need to modify the isSameSlice
calculation to include a check for the layer. This will ensure that the rule only considers slices to be the “same” if they belong to the same layer and have the same name.
The proposed fix involves adding a simple condition to the existing isSameSlice
logic:
const isSameSlice = target.validatedFeatureSlicedParts.hasSlice
&& currentFile.validatedFeatureSlicedParts.hasSlice
&& target.fsdParts.layer === currentFile.fsdParts.layer // Add this check
&& target.fsdParts.slice === currentFile.fsdParts.slice;
By adding target.fsdParts.layer === currentFile.fsdParts.layer
, we ensure that the function only returns true
if the slices are in the same layer. This prevents the rules from incorrectly skipping validation for imports that violate FSD principles.
Configuration Details
For those who are curious about the ESLint configuration used in the affected project, here’s a snippet:
featureSliced({
publicApi: {
level: "segments",
},
}),
{
rules: {
"@conarti/feature-sliced/layers-slices": "warn",
},
}
This configuration utilizes the featureSliced
plugin and sets the publicApi
level to segments
. This means that the plugin should enforce public API boundaries at the segment level, ensuring that components within a slice primarily interact with other slices through their respective public APIs. The layers-slices
rule is set to warn
, meaning that violations will be flagged as warnings but won't prevent the build from succeeding.
Why This Matters: The Importance of Layer Isolation
This bug highlights the importance of enforcing layer isolation in FSD. When layers are allowed to directly import from each other's internal segments, it can lead to tight coupling and a breakdown of the architectural principles that FSD aims to uphold. Imagine a scenario where a component in the pages
layer is directly accessing the internal implementation details of a service in the entities
layer.
If the implementation of that service changes, it could potentially break the component in the pages
layer, even if the public API of the entities
layer remains the same. This kind of tight coupling makes it difficult to refactor and maintain the application, as changes in one area can have unintended consequences in other areas.
By enforcing the use of public APIs, we create a clear separation of concerns and reduce the risk of these kinds of cascading changes. Public APIs act as a contract between layers, providing a stable interface that allows layers to evolve independently. This makes it easier to maintain and scale the application over time.
Impact on Feature Sliced Design
The Feature Sliced Design (FSD) methodology is all about structuring your application in a way that promotes maintainability, scalability, and collaboration. One of the core principles of FSD is the separation of concerns through the use of layers and slices. Layers represent different levels of abstraction within your application, while slices group related features together.
The bug we've discussed directly undermines this principle by allowing components to bypass layer boundaries and directly access internal implementations. This can lead to a tangled codebase that's difficult to understand and maintain. By fixing this bug, we ensure that the ESLint plugin accurately enforces the principles of FSD, helping developers build more robust and maintainable applications.
Real-World Implications
This issue has significant implications for projects that heavily rely on FSD. In real-world applications, it's common to have slices with the same name across different layers. For example, a users
slice might exist in both the entities
layer (representing the domain model) and the features
layer (representing user-related features).
Without the fix, developers might unknowingly import from the internal segments of the users
slice in the entities
layer from components in the features
layer, violating the intended architectural boundaries. This can lead to subtle bugs and make it harder to reason about the codebase. By addressing this issue, we prevent these kinds of accidental violations and ensure that the project adheres to the intended architecture.
Conclusion: A Step Towards Robust FSD Enforcement
In conclusion, this bug in the Feature Sliced ESLint plugin highlights the importance of precise rule implementation when enforcing architectural patterns. The seemingly small oversight in the isSameSlice
calculation had significant implications for the enforcement of FSD principles. By adding a simple check for the layer, we can prevent unintended violations of layer boundaries and ensure that the plugin accurately reflects the intended architecture.
This fix represents a step towards more robust FSD enforcement, helping developers build maintainable and scalable applications. By catching these kinds of issues early in the development process, we can prevent them from becoming larger problems down the road. Remember, guys, a well-structured codebase is key to long-term success!
We hope this deep dive into the bug and its fix has been insightful. Stay tuned for more updates and discussions on Feature Sliced Design and best practices!