Skip to content

Commit

Permalink
Add no-ui-in-app rule (#101)
Browse files Browse the repository at this point in the history
Co-authored-by: Lev Chelyadinov <[email protected]>
  • Loading branch information
Espate and illright authored Sep 19, 2024
1 parent bef439b commit 1bb6c4b
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 0 deletions.
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

0 comments on commit 1bb6c4b

Please sign in to comment.