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

Add no-ui-in-app rule #101

Merged
merged 6 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions .changeset/clean-chicken-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@feature-sliced/steiger-plugin': minor
'steiger': minor
---

Add no-ui-in-app rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Currently, Steiger is not extendable with more rules, though that will change in
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-reserved-folder-names/README.md"><code>no-reserved-folder-names</code></a></td> <td>Forbid subfolders in segments that have the same name as other conventional segments.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-segmentless-slices/README.md"><code>no-segmentless-slices</code></a></td> <td>Forbid slices that don't have any segments.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-segments-on-sliced-layers/README.md"><code>no-segments-on-sliced-layers</code></a></td> <td>Forbid segments (like ui, lib, api ...) that appear directly in sliced layer folders (entities, features, ...)</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-ui-in-app/README.md"><code>no-ui-in-app</code></a></td> <td>Forbid having the <code>ui</code> segment on the App layer.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/public-api/README.md"><code>public-api</code></a></td> <td>Require slices (and segments on sliceless layers like Shared) to have a public API definition.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/repetitive-naming/README.md"><code>repetitive-naming</code></a></td> <td>Ensure that all entities are named consistently in terms of pluralization.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/segments-by-purpose/README.md"><code>segments-by-purpose</code></a></td> <td>Discourage the use of segment names that group code by its essence, and instead encourage grouping by purpose</td> </tr>
Expand Down
2 changes: 2 additions & 0 deletions packages/steiger-plugin-fsd/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import noPublicApiSidestep from './no-public-api-sidestep/index.js'
import noReservedFolderNames from './no-reserved-folder-names/index.js'
import noSegmentlessSlices from './no-segmentless-slices/index.js'
import noSegmentsOnSlicedLayers from './no-segments-on-sliced-layers/index.js'
import noUiInApp from './no-ui-in-app/index.js'
import publicApi from './public-api/index.js'
import repetitiveNaming from './repetitive-naming/index.js'
import segmentsByPurpose from './segments-by-purpose/index.js'
Expand All @@ -28,6 +29,7 @@ const allRules: Array<Rule> = [
noReservedFolderNames,
noSegmentlessSlices,
noSegmentsOnSlicedLayers,
noUiInApp,
publicApi,
repetitiveNaming,
segmentsByPurpose,
Expand Down
43 changes: 43 additions & 0 deletions packages/steiger-plugin-fsd/src/no-ui-in-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# `no-ui-in-app`

Forbid having <code>ui</code> segment in <code>app</code> layer.

Examples of project structures that pass this rule:

```
📂 shared
📂 ui
📄 index.ts
📂 pages
📂 home
📂 ui
📄 index.ts
📂 app
📂 providers
📄 index.ts
```

Examples of project structures that fail this rule:

```
📂 shared
📂 ui
📄 index.ts
📂 pages
📂 home
📂 ui
📄 index.ts
📂 app
📂 providers
📄 index.ts
📂 ui // ❌
📄 index.ts
```

## Rationale

It's uncommon to define the `ui` segment on the App layer. The App layer is typically used to combine the application into a single entry point. The UI of your application should already be created on the layers below to avoid mixing up responsibilities. Therefore, the `ui` segment on the App layer is typically a mistake.

For example, context providers are components, but they are not UI. Global styles are technically UI, but they aren't scoped to that segment, so the name `ui` might be a misdirection.

As one possible exception, the `ui` segment can be used on the App layer if the entire application consists of only one page and there is no reason to define the Pages layer.
46 changes: 46 additions & 0 deletions packages/steiger-plugin-fsd/src/no-ui-in-app/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, it } from 'vitest'

import noUiInApp from './index.js'
import { joinFromRoot, parseIntoFsdRoot } from '../_lib/prepare-test.js'

it('reports no errors on a project without the "ui" segment on the "app" layer', () => {
const root = parseIntoFsdRoot(`
📂 shared
📂 ui
📄 index.ts
📂 pages
📂 home
📂 ui
📄 index.ts
📂 app
📂 providers
📄 index.ts
`)

expect(noUiInApp.check(root)).toEqual({ diagnostics: [] })
})

it('reports errors on a project with the "ui" segment on the "app" layer', () => {
const root = parseIntoFsdRoot(`
📂 shared
📂 ui
📄 index.ts
📂 pages
📂 home
📂 ui
📄 index.ts
📂 app
📂 providers
📄 index.ts
📂 ui
📄 index.ts
`)

const diagnostics = noUiInApp.check(root).diagnostics
expect(diagnostics).toEqual([
{
message: 'Layer "app" should not have "ui" segment.',
location: { path: joinFromRoot('app', 'ui') },
},
])
})
27 changes: 27 additions & 0 deletions packages/steiger-plugin-fsd/src/no-ui-in-app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Diagnostic, Rule } from '@steiger/types'
import { NAMESPACE } from '../constants.js'
import { getLayers, getSegments } from '@feature-sliced/filesystem'

const noUiInApp = {
name: `${NAMESPACE}/no-ui-in-app`,
check(root) {
const diagnostics: Array<Diagnostic> = []

const layers = getLayers(root)

if (layers.app !== undefined) {
const segments = getSegments(layers.app)

if (segments.ui !== undefined) {
diagnostics.push({
message: 'Layer "app" should not have "ui" segment.',
location: { path: segments.ui.path },
})
}
}

return { diagnostics }
},
} satisfies Rule

export default noUiInApp
1 change: 1 addition & 0 deletions packages/steiger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Currently, Steiger is not extendable with more rules, though that will change in
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-reserved-folder-names/README.md"><code>no-reserved-folder-names</code></a></td> <td>Forbid subfolders in segments that have the same name as other conventional segments.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-segmentless-slices/README.md"><code>no-segmentless-slices</code></a></td> <td>Forbid slices that don't have any segments.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-segments-on-sliced-layers/README.md"><code>no-segments-on-sliced-layers</code></a></td> <td>Forbid segments (like ui, lib, api ...) that appear directly in sliced layer folders (entities, features, ...)</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/no-ui-in-app/README.md"><code>no-ui-in-app</code></a></td> <td>Forbid having the <code>ui</code> segment on the App layer.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/public-api/README.md"><code>public-api</code></a></td> <td>Require slices (and segments on sliceless layers like Shared) to have a public API definition.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/repetitive-naming/README.md"><code>repetitive-naming</code></a></td> <td>Ensure that all entities are named consistently in terms of pluralization.</td> </tr>
<tr> <td><a href="./packages/steiger-plugin-fsd/src/segments-by-purpose/README.md"><code>segments-by-purpose</code></a></td> <td>Discourage the use of segment names that group code by its essence, and instead encourage grouping by purpose</td> </tr>
Expand Down
1 change: 1 addition & 0 deletions packages/steiger/migrations/convert-config-to-flat.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const ruleNames = [
'no-reserved-folder-names',
'no-segmentless-slices',
'no-segments-on-sliced-layers',
'no-ui-in-app',
'public-api',
'repetitive-naming',
'segments-by-purpose',
Expand Down