feat: extract templates to JSON and expand preset coverage

- Extract preset definitions from templates.lua into per-framework
  JSON files under lua/project_search/templates/ (common, react, vue, nest)
- templates.lua now loads from JSON via M.load(name), keeping the
  default_rules() API unchanged
- Expand common: add console.log, deprecated, live search presets
- Expand React: add React Query hooks, context providers, Zustand
  stores, shadcn imports, service files/content presets
- Expand Vue: add watch/watchEffect, Pinia stores, provide/inject,
  composables, page components presets
- Expand NestJS: add DTOs, guards, interceptors, decorators,
  entity/module files presets
- Split preset guide into separate en/ and zh/ directories

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
lambert.li
2026-05-20 16:55:14 +08:00
co-authored by Claude Opus 4.7
parent 599598bd65
commit 92d8a93a68
11 changed files with 682 additions and 231 deletions
+2
View File
@@ -9,6 +9,8 @@
- Add interactive live-search preset example to React rule demo.
- Add CLAUDE.md AI agent reference for rule generation.
- Add preset configuration guide (`docs/rule/PRESET_GUIDE.md`).
- Expand built-in templates: add console.log, deprecated, and live search to common; React Query hooks, context providers, Zustand stores, shadcn imports, and service presets to React; watch/watchEffect, Pinia stores, provide/inject, composables, and page components to Vue; DTOs, guards, interceptors, decorators, entity/module files to NestJS.
- Split preset guide into separate `en/` and `zh/` directories.
## v0.1.0
+24 -4
View File
@@ -160,7 +160,10 @@ These are the built-in presets the plugin auto-generates. Use them as a baseline
[
{ "type": "files", "name": "Files: src", "cwd": "src" },
{ "type": "grep", "name": "Search: TODO / FIXME", "search": "TODO|FIXME", "regex": true, "dirs": ["src"] },
{ "type": "grep", "name": "Search: env usage", "search": "process\\.env|import\\.meta\\.env", "regex": true, "dirs": ["src"], "glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"] }
{ "type": "grep", "name": "Search: env usage", "search": "process\\.env|import\\.meta\\.env", "regex": true, "dirs": ["src"], "glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"] },
{ "type": "grep", "name": "Search: console.log", "search": "console\\.log", "regex": true, "dirs": ["src"], "glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], "exclude": ["*.test.*", "*.spec.*"] },
{ "type": "grep", "name": "Search: deprecated", "search": "@deprecated|DEPRECATED", "regex": true, "dirs": ["src"], "glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"] },
{ "type": "grep", "name": "Search: live in src", "search": "", "live": true, "dirs": ["src"] }
]
```
@@ -171,7 +174,13 @@ These are the built-in presets the plugin auto-generates. Use them as a baseline
{ "type": "files_regex", "name": "Hooks: use-* kebab files", "regex": "(^|/)hooks/use-[a-z0-9-]+\\.(ts|tsx|js|jsx)$", "dirs": ["src", "app", "packages"], "exclude": ["node_modules", "dist", "build", ".next"] },
{ "type": "grep", "name": "React: className", "search": "className", "dirs": ["src", "app"], "glob": ["*.tsx", "*.jsx"] },
{ "type": "grep", "name": "React: queryKey", "search": "queryKey", "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"] },
{ "type": "grep", "name": "React: custom hook definitions", "search": "\\bexport\\s+(function|const)\\s+use[A-Z][A-Za-z0-9_]*", "regex": true, "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"] }
{ "type": "grep", "name": "React: custom hook definitions", "search": "\\bexport\\s+(function|const)\\s+use[A-Z][A-Za-z0-9_]*", "regex": true, "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"] },
{ "type": "grep", "name": "React Query: hooks", "search": "useQuery|useMutation|useInfiniteQuery", "regex": true, "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"], "exclude": ["*.test.*", "*.spec.*"] },
{ "type": "grep", "name": "React: context providers", "search": "createContext|useContext", "regex": true, "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"] },
{ "type": "grep", "name": "Zustand: store definitions", "search": "create\\(\\)|create<", "regex": true, "dirs": ["src", "app"], "glob": ["*.ts", "*.tsx"] },
{ "type": "grep", "name": "UI: shadcn component imports", "search": "from\\s+['\"]@/components/ui/", "regex": true, "dirs": ["src", "app"], "glob": ["*.tsx", "*.jsx"] },
{ "type": "files_regex", "name": "Service: API layer files", "regex": "service/[^/]+\\.(ts|tsx)$", "dirs": ["src"], "exclude": ["*.test.*", "*.spec.*"] },
{ "type": "grep", "name": "Search: service content", "search": "", "live": true, "regex": true, "dirs": ["src/service", "src/services", "src/api"], "glob": ["*.ts", "*.tsx"], "exclude": ["*.test.*", "*.spec.*"] }
]
```
@@ -181,7 +190,12 @@ These are the built-in presets the plugin auto-generates. Use them as a baseline
[
{ "type": "grep", "name": "Vue: defineProps", "search": "defineProps", "dirs": ["src"], "glob": ["*.vue", "*.ts"] },
{ "type": "grep", "name": "Vue: defineEmits", "search": "defineEmits", "dirs": ["src"], "glob": ["*.vue", "*.ts"] },
{ "type": "grep", "name": "Vue: ref / computed", "search": "\\b(ref|computed)\\(", "regex": true, "dirs": ["src"], "glob": ["*.vue", "*.ts"] }
{ "type": "grep", "name": "Vue: ref / computed", "search": "\\b(ref|computed)\\(", "regex": true, "dirs": ["src"], "glob": ["*.vue", "*.ts"] },
{ "type": "grep", "name": "Vue: watch / watchEffect", "search": "\\b(watch|watchEffect)\\(", "regex": true, "dirs": ["src"], "glob": ["*.vue", "*.ts"] },
{ "type": "grep", "name": "Pinia: store definitions", "search": "defineStore", "dirs": ["src/stores", "src/store", "src"], "glob": ["*.ts", "*.js"] },
{ "type": "grep", "name": "Vue: provide / inject", "search": "\\b(provide|inject)\\(", "regex": true, "dirs": ["src"], "glob": ["*.vue", "*.ts"] },
{ "type": "files_regex", "name": "Composables: use-* files", "regex": "(^|/)composables/use-[a-z0-9-]+\\.(ts|js)$", "dirs": ["src"] },
{ "type": "files_regex", "name": "Vue: page components", "regex": "(^|/)(pages|views)/[^/]+\\.vue$", "dirs": ["src"] }
]
```
@@ -191,7 +205,13 @@ These are the built-in presets the plugin auto-generates. Use them as a baseline
[
{ "type": "grep", "name": "Nest: Controllers", "search": "@Controller", "dirs": ["src"], "glob": ["*.ts"] },
{ "type": "grep", "name": "Nest: Services", "search": "@Injectable", "dirs": ["src"], "glob": ["*.ts"] },
{ "type": "grep", "name": "Nest: Modules", "search": "@Module", "dirs": ["src"], "glob": ["*.ts"] }
{ "type": "grep", "name": "Nest: Modules", "search": "@Module", "dirs": ["src"], "glob": ["*.ts"] },
{ "type": "grep", "name": "Nest: DTOs", "search": "\\bclass\\s+\\w+Dto\\b", "regex": true, "dirs": ["src"], "glob": ["*.ts"] },
{ "type": "grep", "name": "Nest: Guards", "search": "@Injectable|@UseGuards|CanActivate", "regex": true, "dirs": ["src"], "glob": ["*.guard.ts", "*.ts"] },
{ "type": "grep", "name": "Nest: Interceptors", "search": "@Injectable|NestInterceptor|@UseInterceptors", "regex": true, "dirs": ["src"], "glob": ["*.interceptor.ts", "*.ts"] },
{ "type": "grep", "name": "Nest: custom decorators", "search": "\\bexport\\s+(function|const)\\s+\\w+\\s*=.*createParamDecorator|SetMetadata", "regex": true, "dirs": ["src"], "glob": ["*.decorator.ts", "*.ts"] },
{ "type": "files_regex", "name": "Nest: entity files", "regex": "(^|/)entities?/[^/]+\\.(ts|js)$|(^|/)\\.prisma$", "dirs": ["src"] },
{ "type": "files_regex", "name": "Nest: module files", "regex": "\\.module\\.ts$", "dirs": ["src"] }
]
```
@@ -1,11 +1,7 @@
# Preset Configuration Guide / 规则配置指南
## Overview / 概述
# Preset Configuration Guide
A preset rule file is a JSON file that defines search presets for Project Search. Each preset appears as an item in the picker — select it to run the corresponding search.
规则文件是 JSON 格式,定义了 Project Search 的搜索预设。每个预设会显示在 picker 列表中,选中即可执行对应搜索。
```json
{
"version": 1,
@@ -16,14 +12,12 @@ A preset rule file is a JSON file that defines search presets for Project Search
---
## Preset Types / 预设类型
## Preset Types
### 1. `files` — Browse Files / 浏览文件
### 1. `files` — Browse Files
Open a file picker rooted at a specific directory.
打开指定目录的文件浏览器。
```json
{
"type": "files",
@@ -32,12 +26,10 @@ Open a file picker rooted at a specific directory.
}
```
### 2. `grep` — Search Content / 搜索内容
### 2. `grep` — Search Content
Search file contents with ripgrep. Supports live mode for interactive input.
使用 ripgrep 搜索文件内容,支持 live 模式交互输入。
```json
{
"type": "grep",
@@ -48,12 +40,10 @@ Search file contents with ripgrep. Supports live mode for interactive input.
}
```
### 3. `files_regex` — Match File Paths / 按路径匹配文件
### 3. `files_regex` — Match File Paths
Match files by path pattern using `fd --full-path`. Does not search file contents.
使用 `fd --full-path` 按文件路径模式匹配,不搜索文件内容。
```json
{
"type": "files_regex",
@@ -66,109 +56,97 @@ Match files by path pattern using `fd --full-path`. Does not search file content
---
## All Fields Reference / 全部字段说明
## All Fields Reference
### Common Fields / 公共字段
### Common Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | `"files"` / `"grep"` / `"files_regex"` |
| `name` | string | Yes | Display name in the picker / picker 中的显示名称 |
| `dirs` | string[] | No | Search directories (relative to project root) / 搜索目录(相对于项目根目录) |
| `exclude` | string[] | No | Glob patterns to exclude / 排除的 glob 模式 |
| `hidden` | boolean | No | Include hidden files / 包含隐藏文件 |
| `ignored` | boolean | No | Include gitignored files / 包含 gitignore 忽略的文件 |
| `name` | string | Yes | Display name in the picker |
| `dirs` | string[] | No | Search directories (relative to project root) |
| `exclude` | string[] | No | Glob patterns to exclude |
| `hidden` | boolean | No | Include hidden files |
| `ignored` | boolean | No | Include gitignored files |
### `files` Specific / `files` 专有字段
### `files` Specific
| Field | Type | Description |
|-------|------|-------------|
| `cwd` | string | Set the picker root directory / 设置 picker 的根目录 |
| `cwd` | string | Set the picker root directory |
> `cwd` and `dirs[1]` both control the root directory. `cwd` takes priority.
> `cwd``dirs[1]` 都可控制根目录,`cwd` 优先。
### `grep` Specific / `grep` 专有字段
### `grep` Specific
| Field | Type | Description |
|-------|------|-------------|
| `search` | string | Search term / 搜索关键词 |
| `regex` | boolean | Treat `search` as regex / 将 search 作为正则表达式 |
| `live` | boolean | Open picker with empty input, search as you type / 打开 picker 后输入关键词实时搜索 |
| `glob` | string[] | File glob filters (e.g. `["*.ts", "*.tsx"]`) / 文件 glob 过滤 |
| `args` | string[] | Extra ripgrep arguments / 额外的 ripgrep 参数 |
| `search` | string | Search term |
| `regex` | boolean | Treat `search` as regex |
| `live` | boolean | Open picker with empty input, search as you type |
| `glob` | string[] | File glob filters (e.g. `["*.ts", "*.tsx"]`) |
| `args` | string[] | Extra ripgrep arguments |
### `files_regex` Specific / `files_regex` 专有字段
### `files_regex` Specific
| Field | Type | Description |
|-------|------|-------------|
| `regex` | string | **Required.** Path regex pattern for `fd --full-path` / 路径正则(必须) |
| `regex` | string | **Required.** Path regex pattern for `fd --full-path` |
---
## Field Details / 字段详解
## Field Details
### `dirs` — Search Scope / 搜索范围
### `dirs` — Search Scope
Directories relative to project root. Supports glob wildcards (`*`).
相对于项目根目录的路径,支持 glob 通配符(`*`)。
```json
"dirs": ["src"]
"dirs": ["src", "app", "packages"]
"dirs": ["src/features/*/service"]
```
### `exclude` — Exclude Patterns / 排除模式
### `exclude` — Exclude Patterns
Works for both `grep` and `files_regex`. Merged with global `default_excludes`.
`grep``files_regex` 均支持,会与全局 `default_excludes` 合并。
```json
"exclude": ["node_modules", "dist", "*.test.ts", "*.spec.tsx"]
```
Default excludes (from config) / 默认排除项:
`.git`, `node_modules`, `dist`, `build`, `.next`, `.nuxt`, `coverage`
Default excludes (from config): `.git`, `node_modules`, `dist`, `build`, `.next`, `.nuxt`, `coverage`
### `glob` — File Type Filter / 文件类型过滤
### `glob` — File Type Filter
Only for `grep` type. Restricts search to matching file names.
`grep` 类型支持,限制搜索的文件类型。
```json
"glob": ["*.ts", "*.tsx"]
"glob": ["*.vue", "*.ts"]
```
### `search` — Search Term / 搜索关键词
### `search` — Search Term
For `grep` type. When `live: true`, this can be empty string — the picker opens for you to type.
`grep` 类型专用。当 `live: true` 时可以为空字符串,picker 打开后由你输入。
```json
"search": "className"
"search": "TODO|FIXME"
"search": ""
```
### `regex` — Regex Mode / 正则模式
### `regex` — Regex Mode
| Type | Usage |
|------|-------|
| `grep` | `regex: true` makes `search` a regex / 让 search 作为正则 |
| `files_regex` | `regex` is the path pattern string (required) / 路径匹配正则(必须) |
| `grep` | `regex: true` makes `search` a regex |
| `files_regex` | `regex` is the path pattern string (required) |
### `live` — Interactive Search / 交互式搜索
### `live` — Interactive Search
Only for `grep` type. When `true`, opens the picker with an empty input — type to search in real time.
`grep` 类型。设为 `true` 时,picker 打开后输入关键词实时搜索,无需预设 search 内容。
```json
{
"type": "grep",
@@ -181,24 +159,21 @@ Only for `grep` type. When `true`, opens the picker with an empty input — type
}
```
### `args` — Extra ripgrep Arguments / 额外 ripgrep 参数
### `args` — Extra ripgrep Arguments
Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
直接传递参数给 ripgrep`grep` 类型的底层引擎)。
```json
"args": ["--max-count", "5"]
```
> `exclude` is the recommended way to exclude files. `args` is for advanced use cases only.
> 排除文件推荐用 `exclude``args` 仅用于高级场景。
---
## Complete Examples / 完整示例
## Complete Examples
### Grep: search with live input / 内容搜索(交互输入)
### Grep: search with live input
```json
{
@@ -213,7 +188,7 @@ Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
}
```
### Grep: fixed keyword search / 固定关键词搜索
### Grep: fixed keyword search
```json
{
@@ -225,7 +200,7 @@ Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
}
```
### Grep: regex pattern search / 正则搜索
### Grep: regex pattern search
```json
{
@@ -238,7 +213,7 @@ Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
}
```
### Files Regex: match paths by pattern / 按路径模式匹配
### Files Regex: match paths by pattern
```json
{
@@ -250,7 +225,7 @@ Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
}
```
### Files: browse directory / 浏览目录
### Files: browse directory
```json
{
@@ -262,10 +237,10 @@ Pass custom arguments directly to ripgrep (underlying engine for `grep` type).
---
## Tips / 提示
## Tips
1. **`grep` + `live: true`** — Best for ad-hoc content search in a specific directory / 最适合在指定目录下临时搜索内容
2. **`grep` + `search` (no live)** — Best for frequently searched patterns / 适合高频使用的固定搜索
3. **`files_regex`** — Best for finding files by path convention / 适合按路径命名规范查找文件
4. **`exclude`** — Both `grep` and `files_regex` support it / 两种类型都支持
5. **JSON escaping** — Backslashes in regex must be doubled: `\b``\\b`, `\s``\\s` / JSON 中反斜杠需双写
1. **`grep` + `live: true`** — Best for ad-hoc content search in a specific directory
2. **`grep` + `search` (no live)** — Best for frequently searched patterns
3. **`files_regex`** — Best for finding files by path convention
4. **`exclude`** — Both `grep` and `files_regex` support it
5. **JSON escaping** — Backslashes in regex must be doubled: `\b``\\b`, `\s``\\s`
+246
View File
@@ -0,0 +1,246 @@
# 规则配置指南
规则文件是 JSON 格式,定义了 Project Search 的搜索预设。每个预设会显示在 picker 列表中,选中即可执行对应搜索。
```json
{
"version": 1,
"presets": [ ... ],
"meta": { ... }
}
```
---
## 预设类型
### 1. `files` — 浏览文件
打开指定目录的文件浏览器。
```json
{
"type": "files",
"name": "Files: src",
"cwd": "src"
}
```
### 2. `grep` — 搜索内容
使用 ripgrep 搜索文件内容,支持 live 模式交互输入。
```json
{
"type": "grep",
"name": "React: className",
"search": "className",
"dirs": ["src", "app"],
"glob": ["*.tsx", "*.jsx"]
}
```
### 3. `files_regex` — 按路径匹配文件
使用 `fd --full-path` 按文件路径模式匹配,不搜索文件内容。
```json
{
"type": "files_regex",
"regex": "(^|/)hooks/use-[a-z0-9-]+\\.(ts|tsx)$",
"dirs": ["src", "app"],
"name": "Hooks: use-* files",
"exclude": ["node_modules"]
}
```
---
## 全部字段说明
### 公共字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `type` | string | 是 | `"files"` / `"grep"` / `"files_regex"` |
| `name` | string | 是 | picker 中的显示名称 |
| `dirs` | string[] | 否 | 搜索目录(相对于项目根目录) |
| `exclude` | string[] | 否 | 排除的 glob 模式 |
| `hidden` | boolean | 否 | 包含隐藏文件 |
| `ignored` | boolean | 否 | 包含 gitignore 忽略的文件 |
### `files` 专有字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `cwd` | string | 设置 picker 的根目录 |
> `cwd` 和 `dirs[1]` 都可控制根目录,`cwd` 优先。
### `grep` 专有字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `search` | string | 搜索关键词 |
| `regex` | boolean | 将 search 作为正则表达式 |
| `live` | boolean | 打开 picker 后输入关键词实时搜索 |
| `glob` | string[] | 文件 glob 过滤 |
| `args` | string[] | 额外的 ripgrep 参数 |
### `files_regex` 专有字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `regex` | string | **必填。** 路径正则,传给 `fd --full-path` |
---
## 字段详解
### `dirs` — 搜索范围
相对于项目根目录的路径,支持 glob 通配符(`*`)。
```json
"dirs": ["src"]
"dirs": ["src", "app", "packages"]
"dirs": ["src/features/*/service"]
```
### `exclude` — 排除模式
`grep``files_regex` 均支持,会与全局 `default_excludes` 合并。
```json
"exclude": ["node_modules", "dist", "*.test.ts", "*.spec.tsx"]
```
默认排除项(来自配置):`.git``node_modules``dist``build``.next``.nuxt``coverage`
### `glob` — 文件类型过滤
`grep` 类型支持,限制搜索的文件类型。
```json
"glob": ["*.ts", "*.tsx"]
"glob": ["*.vue", "*.ts"]
```
### `search` — 搜索关键词
`grep` 类型专用。当 `live: true` 时可以为空字符串,picker 打开后由你输入。
```json
"search": "className"
"search": "TODO|FIXME"
"search": ""
```
### `regex` — 正则模式
| 类型 | 用法 |
|------|------|
| `grep` | `regex: true` 让 search 作为正则 |
| `files_regex` | `regex` 是路径匹配正则(必填) |
### `live` — 交互式搜索
`grep` 类型。设为 `true` 时,picker 打开后输入关键词实时搜索,无需预设 search 内容。
```json
{
"type": "grep",
"live": true,
"search": "",
"regex": true,
"name": "Search: service content",
"dirs": ["src/service"],
"glob": ["*.ts", "*.tsx"]
}
```
### `args` — 额外 ripgrep 参数
直接传递参数给 ripgrep`grep` 类型的底层引擎)。
```json
"args": ["--max-count", "5"]
```
> 排除文件推荐用 `exclude`,`args` 仅用于高级场景。
---
## 完整示例
### 内容搜索(交互输入)
```json
{
"type": "grep",
"regex": true,
"live": true,
"name": "Search: service content",
"dirs": ["src/service"],
"search": "",
"glob": ["*.ts", "*.tsx"],
"exclude": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx"]
}
```
### 固定关键词搜索
```json
{
"type": "grep",
"name": "React: className",
"search": "className",
"dirs": ["src", "app"],
"glob": ["*.tsx", "*.jsx"]
}
```
### 正则搜索
```json
{
"type": "grep",
"regex": true,
"name": "React: custom hook definitions",
"search": "\\bexport\\s+(function|const)\\s+use[A-Z]",
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"]
}
```
### 按路径模式匹配
```json
{
"type": "files_regex",
"regex": "service/[^/]+\\.(ts|tsx)$",
"dirs": ["src"],
"name": "Service: API layer files",
"exclude": ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx"]
}
```
### 浏览目录
```json
{
"type": "files",
"name": "Files: src",
"cwd": "src"
}
```
---
## 提示
1. **`grep` + `live: true`** — 最适合在指定目录下临时搜索内容
2. **`grep` + `search`(无 live)** — 适合高频使用的固定搜索
3. **`files_regex`** — 适合按路径命名规范查找文件
4. **`exclude`** — 两种类型都支持
5. **JSON 转义** — JSON 中反斜杠需双写:`\b``\\b``\s``\\s`
+7 -155
View File
@@ -4,158 +4,10 @@ local util = require("project_search.util")
local M = {}
function M.common()
return {
{
id = "common.files.src",
name = "Files: src",
description = "Open a file picker rooted at the project's src directory.",
type = "files",
cwd = "src",
},
{
id = "common.todo_fixme",
name = "Search: TODO / FIXME",
description = "Search TODO and FIXME markers in source files.",
type = "grep",
search = "TODO|FIXME",
regex = true,
dirs = { "src" },
},
{
id = "common.env_usage",
name = "Search: env usage",
description = "Search process.env and import.meta.env references.",
type = "grep",
search = "process\\.env|import\\.meta\\.env",
regex = true,
dirs = { "src" },
glob = { "*.ts", "*.tsx", "*.js", "*.jsx", "*.vue" },
},
}
end
local template_dir = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h") .. "/templates/"
function M.react()
return {
{
id = "react.hooks.kebab",
name = "Hooks: use-* kebab files",
description = "Find hook files such as hooks/use-claw-history-panel.ts.",
type = "files_regex",
regex = "(^|/)hooks/use-[a-z0-9-]+\\.(ts|tsx|js|jsx)$",
dirs = { "src", "app", "packages" },
exclude = { "node_modules", "dist", "build", ".next" },
},
{
id = "react.hooks.claw",
name = "Hooks: claw hooks",
description = "Find hook files whose names start with use-claw-.",
type = "files_regex",
regex = "(^|/)hooks/use-claw-[a-z0-9-]+\\.(ts|tsx)$",
dirs = { "src", "app", "packages" },
},
{
id = "react.hooks.panel",
name = "Hooks: panel hooks",
description = "Find hook files whose names contain panel.",
type = "files_regex",
regex = "(^|/)hooks/use-[a-z0-9-]*panel[a-z0-9-]*\\.(ts|tsx)$",
dirs = { "src", "app", "packages" },
},
{
id = "react.class_name",
name = "React: className",
description = "Search JSX className usage.",
type = "grep",
search = "className",
dirs = { "src", "app" },
glob = { "*.tsx", "*.jsx" },
},
{
id = "react.query_key",
name = "React: queryKey",
description = "Search TanStack Query queryKey usage.",
type = "grep",
search = "queryKey",
dirs = { "src", "app" },
glob = { "*.ts", "*.tsx" },
},
{
id = "react.custom_hook_definitions",
name = "React: custom hook definitions",
description = "Search exported custom hook definitions.",
type = "grep",
search = "\\bexport\\s+(function|const)\\s+use[A-Z][A-Za-z0-9_]*",
regex = true,
dirs = { "src", "app" },
glob = { "*.ts", "*.tsx" },
},
}
end
function M.vue()
return {
{
id = "vue.define_props",
name = "Vue: defineProps",
description = "Search defineProps usage in Vue projects.",
type = "grep",
search = "defineProps",
dirs = { "src" },
glob = { "*.vue", "*.ts" },
},
{
id = "vue.define_emits",
name = "Vue: defineEmits",
description = "Search defineEmits usage in Vue projects.",
type = "grep",
search = "defineEmits",
dirs = { "src" },
glob = { "*.vue", "*.ts" },
},
{
id = "vue.ref_computed",
name = "Vue: ref / computed",
description = "Search ref(...) and computed(...) usage.",
type = "grep",
search = "\\b(ref|computed)\\(",
regex = true,
dirs = { "src" },
glob = { "*.vue", "*.ts" },
},
}
end
function M.nest()
return {
{
id = "nest.controllers",
name = "Nest: Controllers",
description = "Search NestJS controller decorators.",
type = "grep",
search = "@Controller",
dirs = { "src" },
glob = { "*.ts" },
},
{
id = "nest.services",
name = "Nest: Services",
description = "Search NestJS injectable providers.",
type = "grep",
search = "@Injectable",
dirs = { "src" },
glob = { "*.ts" },
},
{
id = "nest.modules",
name = "Nest: Modules",
description = "Search NestJS module decorators.",
type = "grep",
search = "@Module",
dirs = { "src" },
glob = { "*.ts" },
},
}
function M.load(name)
return util.read_json(template_dir .. name .. ".json") or {}
end
local function append(target, source)
@@ -170,19 +22,19 @@ function M.default_rules()
local presets = {}
if enabled.common ~= false then
append(presets, M.common())
append(presets, M.load("common"))
end
if enabled.react ~= false and detector.is_react() then
append(presets, M.react())
append(presets, M.load("react"))
end
if enabled.vue ~= false and detector.is_vue() then
append(presets, M.vue())
append(presets, M.load("vue"))
end
if enabled.nest ~= false and detector.is_nest() then
append(presets, M.nest())
append(presets, M.load("nest"))
end
return {
+58
View File
@@ -0,0 +1,58 @@
[
{
"id": "common.files.src",
"name": "Files: src",
"description": "Open a file picker rooted at the project's src directory.",
"type": "files",
"cwd": "src"
},
{
"id": "common.todo_fixme",
"name": "Search: TODO / FIXME",
"description": "Search TODO and FIXME markers in source files.",
"type": "grep",
"search": "TODO|FIXME",
"regex": true,
"dirs": ["src"]
},
{
"id": "common.env_usage",
"name": "Search: env usage",
"description": "Search process.env and import.meta.env references.",
"type": "grep",
"search": "process\\.env|import\\.meta\\.env",
"regex": true,
"dirs": ["src"],
"glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"]
},
{
"id": "common.console_log",
"name": "Search: console.log",
"description": "Find leftover console.log statements for cleanup.",
"type": "grep",
"search": "console\\.log",
"regex": true,
"dirs": ["src"],
"glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
"exclude": ["*.test.*", "*.spec.*"]
},
{
"id": "common.deprecated",
"name": "Search: deprecated",
"description": "Find @deprecated annotations and deprecated markers.",
"type": "grep",
"search": "@deprecated|DEPRECATED",
"regex": true,
"dirs": ["src"],
"glob": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"]
},
{
"id": "common.live_src",
"name": "Search: live in src",
"description": "Interactive content search across src — type to search in real time.",
"type": "grep",
"search": "",
"live": true,
"dirs": ["src"]
}
]
+85
View File
@@ -0,0 +1,85 @@
[
{
"id": "nest.controllers",
"name": "Nest: Controllers",
"description": "Search NestJS controller decorators.",
"type": "grep",
"search": "@Controller",
"dirs": ["src"],
"glob": ["*.ts"]
},
{
"id": "nest.services",
"name": "Nest: Services",
"description": "Search NestJS injectable providers.",
"type": "grep",
"search": "@Injectable",
"dirs": ["src"],
"glob": ["*.ts"]
},
{
"id": "nest.modules",
"name": "Nest: Modules",
"description": "Search NestJS module decorators.",
"type": "grep",
"search": "@Module",
"dirs": ["src"],
"glob": ["*.ts"]
},
{
"id": "nest.dtos",
"name": "Nest: DTOs",
"description": "Search DTO class definitions.",
"type": "grep",
"search": "\\bclass\\s+\\w+Dto\\b",
"regex": true,
"dirs": ["src"],
"glob": ["*.ts"]
},
{
"id": "nest.guards",
"name": "Nest: Guards",
"description": "Search NestJS guard decorators.",
"type": "grep",
"search": "@Injectable|@UseGuards|CanActivate",
"regex": true,
"dirs": ["src"],
"glob": ["*.guard.ts", "*.ts"]
},
{
"id": "nest.interceptors",
"name": "Nest: Interceptors",
"description": "Search NestJS interceptor patterns.",
"type": "grep",
"search": "@Injectable|NestInterceptor|@UseInterceptors",
"regex": true,
"dirs": ["src"],
"glob": ["*.interceptor.ts", "*.ts"]
},
{
"id": "nest.decorators",
"name": "Nest: custom decorators",
"description": "Search custom decorator definitions.",
"type": "grep",
"search": "\\bexport\\s+(function|const)\\s+\\w+\\s*=.*createParamDecorator|SetMetadata",
"regex": true,
"dirs": ["src"],
"glob": ["*.decorator.ts", "*.ts"]
},
{
"id": "nest.entity_files",
"name": "Nest: entity files",
"description": "Find TypeORM/Prisma entity files by path.",
"type": "files_regex",
"regex": "(^|/)entities?/[^/]+\\.(ts|js)$|(^|/)\\.prisma$",
"dirs": ["src"]
},
{
"id": "nest.module_files",
"name": "Nest: module files",
"description": "Find all NestJS module files by path.",
"type": "files_regex",
"regex": "\\.module\\.ts$",
"dirs": ["src"]
}
]
+117
View File
@@ -0,0 +1,117 @@
[
{
"id": "react.hooks.kebab",
"name": "Hooks: use-* kebab files",
"description": "Find hook files such as hooks/use-claw-history-panel.ts.",
"type": "files_regex",
"regex": "(^|/)hooks/use-[a-z0-9-]+\\.(ts|tsx|js|jsx)$",
"dirs": ["src", "app", "packages"],
"exclude": ["node_modules", "dist", "build", ".next"]
},
{
"id": "react.hooks.claw",
"name": "Hooks: claw hooks",
"description": "Find hook files whose names start with use-claw-.",
"type": "files_regex",
"regex": "(^|/)hooks/use-claw-[a-z0-9-]+\\.(ts|tsx)$",
"dirs": ["src", "app", "packages"]
},
{
"id": "react.hooks.panel",
"name": "Hooks: panel hooks",
"description": "Find hook files whose names contain panel.",
"type": "files_regex",
"regex": "(^|/)hooks/use-[a-z0-9-]*panel[a-z0-9-]*\\.(ts|tsx)$",
"dirs": ["src", "app", "packages"]
},
{
"id": "react.class_name",
"name": "React: className",
"description": "Search JSX className usage.",
"type": "grep",
"search": "className",
"dirs": ["src", "app"],
"glob": ["*.tsx", "*.jsx"]
},
{
"id": "react.query_key",
"name": "React: queryKey",
"description": "Search TanStack Query queryKey usage.",
"type": "grep",
"search": "queryKey",
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"]
},
{
"id": "react.custom_hook_definitions",
"name": "React: custom hook definitions",
"description": "Search exported custom hook definitions.",
"type": "grep",
"search": "\\bexport\\s+(function|const)\\s+use[A-Z][A-Za-z0-9_]*",
"regex": true,
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"]
},
{
"id": "react.react_query_hooks",
"name": "React Query: hooks",
"description": "Search TanStack Query hook usage (useQuery, useMutation, useInfiniteQuery).",
"type": "grep",
"search": "useQuery|useMutation|useInfiniteQuery",
"regex": true,
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"],
"exclude": ["*.test.*", "*.spec.*"]
},
{
"id": "react.context_providers",
"name": "React: context providers",
"description": "Search createContext and useContext usage.",
"type": "grep",
"search": "createContext|useContext",
"regex": true,
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"]
},
{
"id": "react.zustand_stores",
"name": "Zustand: store definitions",
"description": "Search Zustand store creation patterns.",
"type": "grep",
"search": "create\\(\\)|create<",
"regex": true,
"dirs": ["src", "app"],
"glob": ["*.ts", "*.tsx"]
},
{
"id": "react.ui_component_imports",
"name": "UI: shadcn component imports",
"description": "Search imports from @/components/ui/ (shadcn/ui pattern).",
"type": "grep",
"search": "from\\s+['\"]@/components/ui/",
"regex": true,
"dirs": ["src", "app"],
"glob": ["*.tsx", "*.jsx"]
},
{
"id": "react.service_files",
"name": "Service: API layer files",
"description": "Find service/API layer files by path convention.",
"type": "files_regex",
"regex": "service/[^/]+\\.(ts|tsx)$",
"dirs": ["src"],
"exclude": ["*.test.*", "*.spec.*"]
},
{
"id": "react.service_content",
"name": "Search: service content",
"description": "Interactive content search across service directory.",
"type": "grep",
"search": "",
"live": true,
"regex": true,
"dirs": ["src/service", "src/services", "src/api"],
"glob": ["*.ts", "*.tsx"],
"exclude": ["*.test.*", "*.spec.*"]
}
]
+75
View File
@@ -0,0 +1,75 @@
[
{
"id": "vue.define_props",
"name": "Vue: defineProps",
"description": "Search defineProps usage in Vue projects.",
"type": "grep",
"search": "defineProps",
"dirs": ["src"],
"glob": ["*.vue", "*.ts"]
},
{
"id": "vue.define_emits",
"name": "Vue: defineEmits",
"description": "Search defineEmits usage in Vue projects.",
"type": "grep",
"search": "defineEmits",
"dirs": ["src"],
"glob": ["*.vue", "*.ts"]
},
{
"id": "vue.ref_computed",
"name": "Vue: ref / computed",
"description": "Search ref(...) and computed(...) usage.",
"type": "grep",
"search": "\\b(ref|computed)\\(",
"regex": true,
"dirs": ["src"],
"glob": ["*.vue", "*.ts"]
},
{
"id": "vue.watch",
"name": "Vue: watch / watchEffect",
"description": "Search watch and watchEffect usage.",
"type": "grep",
"search": "\\b(watch|watchEffect)\\(",
"regex": true,
"dirs": ["src"],
"glob": ["*.vue", "*.ts"]
},
{
"id": "vue.pinia_stores",
"name": "Pinia: store definitions",
"description": "Search Pinia store definition patterns.",
"type": "grep",
"search": "defineStore",
"dirs": ["src/stores", "src/store", "src"],
"glob": ["*.ts", "*.js"]
},
{
"id": "vue.provide_inject",
"name": "Vue: provide / inject",
"description": "Search provide and inject usage for dependency injection.",
"type": "grep",
"search": "\\b(provide|inject)\\(",
"regex": true,
"dirs": ["src"],
"glob": ["*.vue", "*.ts"]
},
{
"id": "vue.composables",
"name": "Composables: use-* files",
"description": "Find composable files by path convention.",
"type": "files_regex",
"regex": "(^|/)composables/use-[a-z0-9-]+\\.(ts|js)$",
"dirs": ["src"]
},
{
"id": "vue.page_components",
"name": "Vue: page components",
"description": "Find Vue page/view components in pages or views directories.",
"type": "files_regex",
"regex": "(^|/)(pages|views)/[^/]+\\.vue$",
"dirs": ["src"]
}
]
+23 -2
View File
@@ -199,6 +199,9 @@ Negative filters should use exclude instead.
- `Files: src`
- `Search: TODO / FIXME`
- `Search: env usage`
- `Search: console.log`
- `Search: deprecated`
- `Search: live in src`
### react
@@ -208,18 +211,35 @@ Negative filters should use exclude instead.
- `React: className`
- `React: queryKey`
- `React: custom hook definitions`
- `React Query: hooks`
- `React: context providers`
- `Zustand: store definitions`
- `UI: shadcn component imports`
- `Service: API layer files`
- `Search: service content`
### vue
- `Vue: defineProps`
- `Vue: defineEmits`
- `Vue: ref / computed`
- `Vue: watch / watchEffect`
- `Pinia: store definitions`
- `Vue: provide / inject`
- `Composables: use-* files`
- `Vue: page components`
### nest
- `Nest: Controllers`
- `Nest: Services`
- `Nest: Modules`
- `Nest: DTOs`
- `Nest: Guards`
- `Nest: Interceptors`
- `Nest: custom decorators`
- `Nest: entity files`
- `Nest: module files`
## 7. Documentation Status
@@ -229,8 +249,9 @@ Current docs:
README.md
README.zh-CN.md
CLAUDE.md
docs/rule/PRESET_GUIDE.md
docs/rule/react-rule.json
docs/rule/en/PRESET_GUIDE.md
docs/rule/zh/PRESET_GUIDE.md
docs/rule/en/react-rule.json
docs/assets/react-project-search.png
docs/assets/react-service-dir-search.png
```