Skip to content
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
3 changes: 2 additions & 1 deletion .releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"writerOpts": {
"commitsSort": ["subject", "scope"]
}
},
"excludeMergeCommits": true
}
],
"@semantic-release/npm",
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ This plugin provides rules to enforce modular architecture boundaries in Vue.js

> The list of rules is a work in progress. Implemented rules are linked below; unimplemented rules are listed as plain names.
>
> ![Progress](https://progress-bar.xyz/6/?scale=92&width=500&title=6%20of%2092%20rules%20completed)
> ![Progress](https://progress-bar.xyz/8/?scale=92&width=500&title=8%20of%2092%20rules%20completed)

### File Organization Rules

Expand All @@ -130,17 +130,17 @@ This plugin provides rules to enforce modular architecture boundaries in Vue.js
| app-imports | `app/` folder can import from `shared/` and `features/` (exception: `app/router.ts` may import feature route files to compose the global router). |
| cross-feature-via-shared | All cross-feature communication must go through the `shared/` layer. |

### Component Rules

| Rule | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| sfc-required | All Vue components should be written as Single File Components (SFC) with `.vue` extension; when present on disk a `.vue` file must contain at least a `<template>` or `<script>` / `<script setup>` block. |
| sfc-order | Enforce SFC block order: `<script>`, `<template>`, `<style>`; the rule only enforces ordering when script/template blocks are present. |
| layout-components-location | Layout-specific components must be in `app/components/`. |
| ui-components-location | Reusable UI components (design system) must be in `shared/ui/`. |
| business-components-location | Business components used across features must be in `shared/components/`. |
| feature-components-location | Feature-specific components must be in `features/{feature}/components/`. |
| component-props-typed | Component props must be typed with TypeScript interfaces. |
### Components Rules

| Rule | Description |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [sfc-required](./docs/rules/sfc-required.md) | All Vue components should be written as Single File Components (SFC) with `.vue` extension; when present on disk a `.vue` file must contain at least a `<template>` or `<script>` / `<script setup>` block. |
| [sfc-order](./docs/rules/sfc-order.md) | Enforce SFC block order: `<script>`, `<template>`, `<style>`; the rule only enforces ordering when script/template blocks are present. |
| layout-components-location | Layout-specific components must be in `app/components/`. |
| ui-components-location | Reusable UI components (design system) must be in `shared/ui/`. |
| business-components-location | Business components used across features must be in `shared/components/`. |
| feature-components-location | Feature-specific components must be in `features/{feature}/components/`. |
| component-props-typed | Component props must be typed with TypeScript interfaces. |

### Service Rules

Expand Down
111 changes: 91 additions & 20 deletions docs/rules/sfc-order.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,136 @@
# vue-modular/sfc-order

Require a conventional order for Single File Component (SFC) blocks when those blocks exist.
Enforce a conventional order for Single File Component (SFC) blocks.

This rule parses `.vue` files using `@vue/compiler-sfc` and validates the relative order of blocks that are present in the file. Missing blocks are allowed — only the relative order among present blocks is checked.
This rule parses Vue component files using `@vue/compiler-sfc` and validates the relative order of SFC blocks (`<script>`, `<template>`, `<style>`). The rule only enforces order among blocks that are present — missing blocks are allowed.

By default the recommended order is:
Default order:

1. `<script>` / `<script setup>`
2. `<template>`
3. `<style>` (one or more)

## Rule Details

- The rule runs only for on-disk `.vue` files under the configured `src` segment (default: `src`).
- Virtual inputs (filenames starting with `<...>`) and files matched by the `ignore` option are skipped.
- The rule computes the actual block order from the SFC descriptor and validates that the sequence of present blocks respects the relative order defined by the `order` option (or the default order).
- This rule intentionally does not report missing blocks — use `vue-modular/sfc-required` to enforce presence of `<template>`/`<script>`.
- **Component detection**: Only runs on `.vue` files located in the configured components folder. Files outside component folders are ignored.
- **SFC parsing**: Uses `@vue/compiler-sfc` to parse the Vue file and extract block positions.
- **Order validation**: Checks that present blocks follow the configured relative order. Missing blocks are allowed.
- **Early exit**: If a component has neither `<template>` nor `<script>` blocks, the rule skips validation (no meaningful content to order).

## Options

The rule accepts a single options object:

- `src` (string, default: `"src"`) — path segment used to identify the repository source area where the rule should run.
- `ignore` (string[], default: `[]`) — minimatch-style patterns to ignore files or folders.
- `order` (string[], default: `['script','template','style']`) — the desired relative block ordering. Allowed values are `"script"`, `"template"`, and `"style"`. Missing blocks are permitted.
- `order` (string[], default: `["script", "template", "style"]`) — desired relative block ordering. Allowed values: `"script"`, `"template"`, `"style"`
- `ignores` (string[], default: `[""]`) — minimatch-style patterns to exclude specific files

### Project Configuration

The rule uses project-wide settings from your ESLint configuration's `settings['vue-modular']`:

- `componentsFolderName` (string, default: `"components"`) — name of the folder that contains components
- `rootPath` (string, default: `"src"`) — root path of the project
- `rootAlias` (string, default: `"@"`) — alias for the root path

### Example configuration

```js
// ESLint config
{
"vue-modular/sfc-order": ["error", { "src": "src", "ignore": [], "order": ["script", "template", "style"] }]
"settings": {
"vue-modular": {
"componentsFolderName": "components",
"rootPath": "src",
"rootAlias": "@"
}
},
"rules": {
"vue-modular/sfc-order": ["error", {
"order": ["script", "template", "style"],
"ignores": []
}]
}
}
```

## Incorrect

```vue
<!-- Wrong order: template before script -->
<template>
<div />
<div>Hello</div>
</template>
<script>
export default {}
<script setup>
const message = 'Hello'
</script>
```

```vue
<!-- Wrong order: style before template -->
<script setup>
const message = 'Hello'
</script>
<style>
.hello {
color: blue;
}
</style>
<template>
<div>Hello</div>
</template>
```

## Correct

```vue
<!-- Correct order: script, template, style -->
<script setup>
const a = 1
const message = 'Hello'
</script>
<template>
<div />
<div>{{ message }}</div>
</template>
<style>
/* css */
.hello {
color: blue;
}
</style>
```

```vue
<!-- Missing blocks are allowed -->
<script setup>
// Script-only component
export function useUtils() {}
</script>
```

```vue
<!-- Custom order example -->
<style>
/* Custom order: style first */
.component {
display: block;
}
</style>
<script setup>
const props = defineProps({})
</script>
<template>
<div class="component">Content</div>
</template>
```

## Usage Notes

- The rule uses `@vue/compiler-sfc` to parse SFCs, so `<script setup>` and other modern SFC syntax are handled correctly.
- The rule checks order only; it does not assert the presence of template/script blocks. Use `vue-modular/sfc-required` to require those blocks.
- The `ignore` option accepts minimatch patterns — match feature folder names (for example `"**/legacy/**"`) to skip legacy areas.
- The rule uses `@vue/compiler-sfc` to parse Vue files reliably, supporting `<script setup>` and other modern SFC syntax.
- Only files identified as components (`.vue` files in the components folder) are checked.
- Both `<script>` and `<script setup>` blocks are treated as `"script"` type for ordering purposes.
- Multiple `<style>` blocks are allowed and treated as individual `"style"` blocks.
- Files with only style blocks (no template or script) are skipped entirely.
- Files can be excluded using the `ignores` option with minimatch patterns.

## When Not To Use

- Disable this rule if your project doesn't follow consistent SFC block ordering conventions.
- Use the `ignores` option to exclude specific files or patterns rather than disabling the rule entirely.
74 changes: 53 additions & 21 deletions docs/rules/sfc-required.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,67 @@
# vue-modular/sfc-required

Require Single File Component (SFC) structure for Vue component files under a configured `src` segment.
Require Vue Single File Component (SFC) files to contain at least a `<template>` or `<script>` block.

This rule enforces that any Vue component file that lives under the configured `src` segment uses the `.vue` SFC format and, when the file is present on disk, contains at least one of the meaningful SFC blocks (`<template>` or `<script>` / `<script setup>`). Files that are virtual (like `"<input>"`) or explicitly ignored by the rule's `ignore` option are skipped.
This rule enforces that Vue component files contain meaningful content by requiring at least one of the core SFC blocks (`<template>` or `<script>` / `<script setup>`). It only runs on files that are identified as Vue components (`.vue` files in the configured components folder).

## Rule Details

The rule performs two related checks:
The rule performs the following checks:

1. It only runs for files inside the configured `src` segment (default: `src`). Files outside that segment are ignored.
2. For on-disk `.vue` files it parses the SFC using `@vue/compiler-sfc` and verifies the descriptor contains either a `<template>` block or a `<script>` / `<script setup>` block. If neither block exists the rule reports `missingSfcBlock` with the filename.
1. **Component detection**: Only runs on `.vue` files located in the configured components folder (default: `components`). Files outside component folders are ignored.
2. **SFC parsing**: Uses `@vue/compiler-sfc` to parse the Vue file and extract the SFC descriptor.
3. **Block validation**: Verifies that the SFC contains either a `<template>` block or a `<script>`/`<script setup>` block. If neither exists, reports an error.

This behavior helps catch accidentally empty `.vue` files or files that only contain style blocks (for example UI kit style-only files that should not be placed inside feature code), while avoiding false positives for virtual or non-.vue files.
This helps catch accidentally empty component files or files that only contain style blocks without meaningful component logic.

## Options

The rule accepts a single options object:

- `src` (string, default: `"src"`) — the path segment used to identify the codebase root under which the rule should run. The rule finds the configured segment in the linted file path and only enforces rules for files under that segment.
- `ignore` (string[], default: `[]`) — minimatch-style patterns matched against the relative path (or parent segment) to skip files or features. Use this to exclude legacy folders or third-party directories.
- `ignores` (string[], default: `[""]`) — minimatch-style patterns to exclude specific files from checking.

### Project Configuration

The rule uses project-wide settings from your ESLint configuration's `settings['vue-modular']`:

- `componentsFolderName` (string, default: `"components"`) — name of the folder that contains components
- `rootPath` (string, default: `"src"`) — root path of the project
- `rootAlias` (string, default: `"@"`) — alias for the root path

### Example configuration

```js
// Recommended defaults: scan files under `src` and do not ignore anything
// ESLint config
{
"vue-modular/sfc-required": ["error", { "src": "src", "ignore": [] }]
"settings": {
"vue-modular": {
"componentsFolderName": "components",
"rootPath": "src",
"rootAlias": "@"
}
},
"rules": {
"vue-modular/sfc-required": ["error", { "ignores": [] }]
}
}
```

## Incorrect

```text
// File: src/features/payments/components/Orphan.vue
// File contents: only style and no template or script blocks
```vue
<!-- File: src/components/EmptyComponent.vue -->
<!-- Only style block, no template or script -->
<style>
/* only styles */
.card {
padding: 1rem;
}
</style>
```

## Correct

```vue
<!-- File: src/features/payments/components/Card.vue -->
<!-- File: src/components/Card.vue -->
<template>
<div class="card">...</div>
</template>
Expand All @@ -52,17 +71,30 @@ const props = defineProps({})
</script>

<style>
/* styles */
.card {
padding: 1rem;
}
</style>
```

```vue
<!-- File: src/components/Utility.vue -->
<!-- Script-only component is also valid -->
<script setup>
export function useCardUtils() {
// utility logic
}
</script>
```

## Usage Notes

- The rule uses `@vue/compiler-sfc` to parse the `.vue` file reliably; this allows detection of `<script setup>` and other valid SFC syntax.
- The rule runs only once per ESLint process using the plugin's `runOnce` helper to avoid duplicate scans and noise when linting many files.
- If a `.vue` file is missing on disk (for example an IDE virtual document) the rule will skip the content check.
- The `ignore` option accepts minimatch patterns; match the feature folder name (for example `"**/legacy/**"`) to skip legacy areas.
- The rule uses `@vue/compiler-sfc` to parse Vue files reliably, supporting `<script setup>` and other modern SFC syntax.
- Only files identified as components (`.vue` files in the components folder) are checked.
- Files can be excluded using the `ignores` option with minimatch patterns.
- The rule respects project-wide component folder configuration via ESLint settings.

## When Not To Use

- Do not enable this rule for folders that intentionally contain non-SFC Vue artifacts (for example a style-only UI-kit folder that you choose to keep outside of features). Instead, configure `ignore` to skip those paths.
- Disable this rule if you intentionally create Vue files with only style blocks (e.g., style-only component libraries).
- Use the `ignores` option to exclude specific files or patterns rather than disabling the rule entirely.
1 change: 0 additions & 1 deletion src/rules/file-component-naming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const fileComponentNaming = createRule<VueModularRuleModule>({
{
type: 'object',
properties: {
src: { type: 'string' },
ignores: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
Expand Down
1 change: 0 additions & 1 deletion src/rules/file-ts-naming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export const fileTsNaming = createRule<VueModularRuleModule>({
{
type: 'object',
properties: {
src: { type: 'string' },
ignores: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
Expand Down
1 change: 0 additions & 1 deletion src/rules/folder-kebab-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const folderKebabCase = createRule<VueModularRuleModule>({
{
type: 'object',
properties: {
src: { type: 'string' },
ignores: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
Expand Down
Loading