Skip to content
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

Make sure forbidden-imports rule checks files directly inside layers #64

Merged
5 changes: 5 additions & 0 deletions .changeset/quick-eggs-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@feature-sliced/steiger-plugin': patch
---

Make sure forbidden-imports rule checks files directly inside layers
39 changes: 39 additions & 0 deletions packages/steiger-plugin-fsd/src/_lib/index-source-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export function indexSourceFiles(root: Folder): Record<string, SourceFile> {
}

for (const [layerName, layer] of Object.entries(getLayers(root))) {
// Even though files that are directly inside a layer are not encouraged by the FSD and are forbidden in most cases
// (except for an index/root file for the app layer as an entry point to the application), users can still add them.
// So, we need to index all files directly inside a layer to find errors.
layer.children
.filter((child) => child.type === 'file')
.forEach((file) => walk(file, { layerName: layerName as LayerName, sliceName: null, segmentName: null }))
daniilsapa marked this conversation as resolved.
Show resolved Hide resolved

if (!isSliced(layer)) {
for (const [segmentName, segment] of Object.entries(getSegments(layer))) {
walk(segment, { layerName: layerName as LayerName, sliceName: null, segmentName })
Expand Down Expand Up @@ -70,6 +77,11 @@ if (import.meta.vitest) {
📄 EditorPage.tsx
📄 Editor.tsx
📄 index.ts
📂 app
📂 ui
📄 index.ts
📄 root.ts
📄 index.ts
`)

expect(indexSourceFiles(root)).toEqual({
Expand Down Expand Up @@ -163,6 +175,33 @@ if (import.meta.vitest) {
segmentName: 'ui',
sliceName: null,
},
[joinFromRoot('app', 'ui', 'index.ts')]: {
file: {
path: joinFromRoot('app', 'ui', 'index.ts'),
type: 'file',
},
layerName: 'app',
segmentName: 'ui',
sliceName: null,
},
[joinFromRoot('app', 'root.ts')]: {
file: {
path: joinFromRoot('app', 'root.ts'),
type: 'file',
},
layerName: 'app',
segmentName: 'root',
sliceName: null,
},
[joinFromRoot('app', 'index.ts')]: {
file: {
path: joinFromRoot('app', 'index.ts'),
type: 'file',
},
layerName: 'app',
segmentName: null,
sliceName: null,
},
})
})
}
89 changes: 86 additions & 3 deletions packages/steiger-plugin-fsd/src/_lib/prepare-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import type { FsdRoot } from '@feature-sliced/filesystem'
import type { Folder, File, Diagnostic } from '@steiger/types'
import { vi } from 'vitest'

/** Parse a multi-line indented string with emojis for files and folders into an FSD root. */
export function parseIntoFsdRoot(fsMarkup: string): FsdRoot {
/** Parse a multi-line indented string with emojis for files and folders into an FSD root.
* @param fsMarkup - a file system tree represented in markup using file and folder emojis
* @param mountTo - virtually make the passed markup a subtree of the mountTo folder
* */
export function parseIntoFsdRoot(fsMarkup: string, mountTo?: string): FsdRoot {
function parseFolder(lines: Array<string>, path: string): Folder {
const children: Array<Folder | File> = []

Expand Down Expand Up @@ -33,7 +36,7 @@ export function parseIntoFsdRoot(fsMarkup: string): FsdRoot {
.map((line, _i, lines) => line.slice(lines[0].search(/\S/)))
.filter(Boolean)

return parseFolder(lines, joinFromRoot())
return parseFolder(lines, mountTo ?? joinFromRoot())
}

export function compareMessages(a: Diagnostic, b: Diagnostic): number {
Expand Down Expand Up @@ -149,4 +152,84 @@ if (import.meta.vitest) {
],
})
})

test('it should return a nested root folder when the optional rootPath argument is passed', () => {
const markup = `
📂 entities
📂 users
📂 ui
📄 index.ts
📂 posts
📂 ui
📄 index.ts
📂 shared
📂 ui
📄 index.ts
📄 Button.tsx
`
const root = parseIntoFsdRoot(markup, joinFromRoot('src'))

expect(root).toEqual({
type: 'folder',
path: joinFromRoot('src'),
children: [
{
type: 'folder',
path: joinFromRoot('src', 'entities'),
children: [
{
type: 'folder',
path: joinFromRoot('src', 'entities', 'users'),
children: [
{
type: 'folder',
path: joinFromRoot('src', 'entities', 'users', 'ui'),
children: [],
},
{
type: 'file',
path: joinFromRoot('src', 'entities', 'users', 'index.ts'),
},
],
},
{
type: 'folder',
path: joinFromRoot('src', 'entities', 'posts'),
children: [
{
type: 'folder',
path: joinFromRoot('src', 'entities', 'posts', 'ui'),
children: [],
},
{
type: 'file',
path: joinFromRoot('src', 'entities', 'posts', 'index.ts'),
},
],
},
],
},
{
type: 'folder',
path: joinFromRoot('src', 'shared'),
children: [
{
type: 'folder',
path: joinFromRoot('src', 'shared', 'ui'),
children: [
{
type: 'file',
path: joinFromRoot('src', 'shared', 'ui', 'index.ts'),
},
{
type: 'file',
path: joinFromRoot('src', 'shared', 'ui', 'Button.tsx'),
},
],
},
],
},
],
})
})
}
Loading