tha-cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +58 -33
- package/README.md +40 -26
- package/package.json +1 -1
- package/tha/cli.mjs +2 -0
- package/tha/commands/check.mjs +17 -18
- package/tha/commands/init.mjs +112 -0
- package/tha/commands/watch.mjs +67 -0
- package/tha/eslint-rules/executor-no-cross-import.js +53 -0
- package/tha/eslint-rules/executor-no-import-api.js +69 -0
- package/tha/eslint-rules/executor-no-import-utils.js +68 -0
- package/tha/eslint-rules/hub-no-import-steps.js +51 -0
- package/tha/eslint-rules/index.js +42 -0
- package/tha/eslint-rules/store-no-import-api.js +57 -0
- package/tha/eslint-rules/util-no-import-ui.js +78 -0
- package/tha/eslint-rules/view-no-create-module.js +68 -0
- package/tha/eslint-rules/view-no-import-api.js +87 -0
- package/tha/utils/eslint.mjs +288 -0
- package/tha/verify/boundary.mjs +0 -219
- package/tha/verify/rules.mjs +0 -91
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXE-02:执行器/零件不许 import 网络层、状态层、路由
|
|
3
|
+
* 所有依赖必须通过 ctx 注入,不自己 import
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
domains: 'src/domains',
|
|
8
|
+
api: 'src/api',
|
|
9
|
+
stores: 'src/stores',
|
|
10
|
+
router: 'src/router'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getDirs(context) {
|
|
14
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isExecutorOrStepFile(filename, dirs) {
|
|
18
|
+
const domainsDir = dirs.domains.replace(/\\/g, '/')
|
|
19
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
20
|
+
// 文件在 domains/<模块>/executors/ 或 domains/<模块>/steps/ 下
|
|
21
|
+
return new RegExp(`/${domainsDir}/[^/]+/(executors|steps)/`).test(normalized)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function importSpec(dirs, key) {
|
|
25
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
26
|
+
return `@/${base}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'problem',
|
|
32
|
+
docs: {
|
|
33
|
+
description: '执行器/零件不许 import 网络层、状态层、路由,依赖必须通过 ctx 注入',
|
|
34
|
+
recommended: true
|
|
35
|
+
},
|
|
36
|
+
schema: []
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
create(context) {
|
|
40
|
+
const dirs = getDirs(context)
|
|
41
|
+
const filename = context.filename
|
|
42
|
+
|
|
43
|
+
if (!isExecutorOrStepFile(filename, dirs)) return {}
|
|
44
|
+
|
|
45
|
+
const forbiddenPrefixes = [
|
|
46
|
+
importSpec(dirs, 'api'),
|
|
47
|
+
importSpec(dirs, 'stores'),
|
|
48
|
+
importSpec(dirs, 'router'),
|
|
49
|
+
'@/store'
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
ImportDeclaration(node) {
|
|
54
|
+
const importPath = node.source.value
|
|
55
|
+
|
|
56
|
+
const matched = forbiddenPrefixes.find(
|
|
57
|
+
(prefix) => importPath === prefix || importPath.startsWith(`${prefix}/`)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (matched) {
|
|
61
|
+
context.report({
|
|
62
|
+
node,
|
|
63
|
+
message: `执行器不许直接 import ${matched},依赖必须通过 ctx 注入`
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXE-03:执行器/零件不许 import utils/(纯函数白名单除外)
|
|
3
|
+
* 日志/时钟/流/鉴权这些必须经 ctx.ports 注入
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
domains: 'src/domains',
|
|
8
|
+
utils: 'src/utils'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 纯函数白名单:无副作用、不持有状态、不依赖运行时环境
|
|
12
|
+
const PURE_UTILS = ['@/utils/adapters', '@/utils/format', '@/utils/number']
|
|
13
|
+
|
|
14
|
+
function getDirs(context) {
|
|
15
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isExecutorOrStepFile(filename, dirs) {
|
|
19
|
+
const domainsDir = dirs.domains.replace(/\\/g, '/')
|
|
20
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
21
|
+
return new RegExp(`/${domainsDir}/[^/]+/(executors|steps)/`).test(normalized)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function importSpec(dirs, key) {
|
|
25
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
26
|
+
return `@/${base}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'problem',
|
|
32
|
+
docs: {
|
|
33
|
+
description: '执行器/零件不许 import utils/,能力必须经 ctx.ports 注入',
|
|
34
|
+
recommended: true
|
|
35
|
+
},
|
|
36
|
+
schema: []
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
create(context) {
|
|
40
|
+
const dirs = getDirs(context)
|
|
41
|
+
const filename = context.filename
|
|
42
|
+
|
|
43
|
+
if (!isExecutorOrStepFile(filename, dirs)) return {}
|
|
44
|
+
|
|
45
|
+
const utilsBase = importSpec(dirs, 'utils')
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
ImportDeclaration(node) {
|
|
49
|
+
const importPath = node.source.value
|
|
50
|
+
|
|
51
|
+
// 不是 import utils/ 的,跳过
|
|
52
|
+
if (!importPath.startsWith(`${utilsBase}/`) && importPath !== utilsBase) return
|
|
53
|
+
|
|
54
|
+
// 白名单里的纯函数,放行
|
|
55
|
+
const isPure = PURE_UTILS.some(
|
|
56
|
+
(pure) => importPath === pure || importPath.startsWith(`${pure}/`)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (!isPure) {
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
message: `执行器不许直接 import ${importPath},能力必须经 ctx.ports 注入`
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HUB-01:中枢不许 import steps/
|
|
3
|
+
* 中枢只做分发,把意图交给执行器,不直接调内部步骤
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
domains: 'src/domains'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getDirs(context) {
|
|
11
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isHubFile(filename, dirs) {
|
|
15
|
+
const domainsDir = dirs.domains.replace(/\\/g, '/')
|
|
16
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
17
|
+
// 文件是 domains/<模块>/index.ts(中枢入口)
|
|
18
|
+
return new RegExp(`/${domainsDir}/[^/]+/index\\.ts$`).test(normalized)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'problem',
|
|
24
|
+
docs: {
|
|
25
|
+
description: '中枢不许 import steps/,只做分发,把意图交给执行器',
|
|
26
|
+
recommended: true
|
|
27
|
+
},
|
|
28
|
+
schema: []
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
create(context) {
|
|
32
|
+
const dirs = getDirs(context)
|
|
33
|
+
const filename = context.filename
|
|
34
|
+
|
|
35
|
+
if (!isHubFile(filename, dirs)) return {}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ImportDeclaration(node) {
|
|
39
|
+
const importPath = node.source.value
|
|
40
|
+
|
|
41
|
+
// import 了 steps/ 下的文件
|
|
42
|
+
if (/^(\.\.?\/)+steps\//.test(importPath)) {
|
|
43
|
+
context.report({
|
|
44
|
+
node,
|
|
45
|
+
message: '中枢不许直接 import steps/,把这类控制动作放进执行器文件'
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tha-cli ESLint 插件
|
|
3
|
+
* 把 THA 架构的边界规则做成 ESLint 规则,编辑器里实时红线
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import hubNoImportSteps from './hub-no-import-steps.js'
|
|
7
|
+
import executorNoCrossImport from './executor-no-cross-import.js'
|
|
8
|
+
import executorNoImportApi from './executor-no-import-api.js'
|
|
9
|
+
import executorNoImportUtils from './executor-no-import-utils.js'
|
|
10
|
+
import viewNoImportApi from './view-no-import-api.js'
|
|
11
|
+
import viewNoCreateModule from './view-no-create-module.js'
|
|
12
|
+
import storeNoImportApi from './store-no-import-api.js'
|
|
13
|
+
import utilNoImportUi from './util-no-import-ui.js'
|
|
14
|
+
|
|
15
|
+
export const rules = {
|
|
16
|
+
'hub-no-import-steps': hubNoImportSteps,
|
|
17
|
+
'executor-no-cross-import': executorNoCrossImport,
|
|
18
|
+
'executor-no-import-api': executorNoImportApi,
|
|
19
|
+
'executor-no-import-utils': executorNoImportUtils,
|
|
20
|
+
'view-no-import-api': viewNoImportApi,
|
|
21
|
+
'view-no-create-module': viewNoCreateModule,
|
|
22
|
+
'store-no-import-api': storeNoImportApi,
|
|
23
|
+
'util-no-import-ui': utilNoImportUi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const configs = {
|
|
27
|
+
recommended: {
|
|
28
|
+
plugins: ['tha'],
|
|
29
|
+
rules: {
|
|
30
|
+
'tha/hub-no-import-steps': 'error',
|
|
31
|
+
'tha/executor-no-cross-import': 'error',
|
|
32
|
+
'tha/executor-no-import-api': 'error',
|
|
33
|
+
'tha/executor-no-import-utils': 'error',
|
|
34
|
+
'tha/view-no-import-api': 'error',
|
|
35
|
+
'tha/view-no-create-module': 'error',
|
|
36
|
+
'tha/store-no-import-api': 'error',
|
|
37
|
+
'tha/util-no-import-ui': 'error'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default { rules, configs }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STORE-01:状态层不许 import api 层
|
|
3
|
+
* 状态层只存状态,不发请求
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
stores: 'src/stores',
|
|
8
|
+
api: 'src/api'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getDirs(context) {
|
|
12
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isStoreFile(filename, dirs) {
|
|
16
|
+
const storesDir = dirs.stores.replace(/\\/g, '/')
|
|
17
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
18
|
+
return new RegExp(`/${storesDir}/`).test(normalized)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function importSpec(dirs, key) {
|
|
22
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
23
|
+
return `@/${base}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
meta: {
|
|
28
|
+
type: 'problem',
|
|
29
|
+
docs: {
|
|
30
|
+
description: '状态层不许 import api 层,状态层只存状态,不发请求',
|
|
31
|
+
recommended: true
|
|
32
|
+
},
|
|
33
|
+
schema: []
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
create(context) {
|
|
37
|
+
const dirs = getDirs(context)
|
|
38
|
+
const filename = context.filename
|
|
39
|
+
|
|
40
|
+
if (!isStoreFile(filename, dirs)) return {}
|
|
41
|
+
|
|
42
|
+
const apiBase = importSpec(dirs, 'api')
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
ImportDeclaration(node) {
|
|
46
|
+
const importPath = node.source.value
|
|
47
|
+
|
|
48
|
+
if (importPath.startsWith(`${apiBase}/`)) {
|
|
49
|
+
context.report({
|
|
50
|
+
node,
|
|
51
|
+
message: '状态层不许 import api 层,把请求挪进执行器,拿到结果再写回 state'
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UTIL-01:基础设施不许 import UI 库或业务模块
|
|
3
|
+
* 传输层只上报不越权:不感知业务模块,不做展示
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
utils: 'src/utils',
|
|
8
|
+
views: 'src/views',
|
|
9
|
+
domains: 'src/domains'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getDirs(context) {
|
|
13
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isUtilFile(filename, dirs) {
|
|
17
|
+
const utilsDir = dirs.utils.replace(/\\/g, '/')
|
|
18
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
19
|
+
return new RegExp(`/${utilsDir}/`).test(normalized)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function importSpec(dirs, key) {
|
|
23
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
24
|
+
return `@/${base}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
meta: {
|
|
29
|
+
type: 'problem',
|
|
30
|
+
docs: {
|
|
31
|
+
description: '基础设施不许 import UI 库或业务模块',
|
|
32
|
+
recommended: true
|
|
33
|
+
},
|
|
34
|
+
schema: []
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
create(context) {
|
|
38
|
+
const dirs = getDirs(context)
|
|
39
|
+
const filename = context.filename
|
|
40
|
+
|
|
41
|
+
if (!isUtilFile(filename, dirs)) return {}
|
|
42
|
+
|
|
43
|
+
const viewsBase = importSpec(dirs, 'views')
|
|
44
|
+
const domainsBase = importSpec(dirs, 'domains')
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
ImportDeclaration(node) {
|
|
48
|
+
const importPath = node.source.value
|
|
49
|
+
|
|
50
|
+
// import 了 UI 库
|
|
51
|
+
if (importPath === 'element-plus') {
|
|
52
|
+
context.report({
|
|
53
|
+
node,
|
|
54
|
+
message: '基础设施不许 import UI 库,提示交给页面'
|
|
55
|
+
})
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// import 了视图层
|
|
60
|
+
if (importPath.startsWith(`${viewsBase}/`)) {
|
|
61
|
+
context.report({
|
|
62
|
+
node,
|
|
63
|
+
message: '基础设施不许 import 视图层'
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// import 了业务模块
|
|
69
|
+
if (importPath.startsWith(`${domainsBase}/`)) {
|
|
70
|
+
context.report({
|
|
71
|
+
node,
|
|
72
|
+
message: '基础设施不许 import 业务模块'
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIEW-02:视图不许自己 create 模块实例
|
|
3
|
+
* 每个领域全局唯一实例,视图只能经 useXxxModule() 取
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
views: 'src/views',
|
|
8
|
+
domains: 'src/domains'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getDirs(context) {
|
|
12
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isViewFile(filename, dirs) {
|
|
16
|
+
const viewsDir = dirs.views.replace(/\\/g, '/')
|
|
17
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
18
|
+
return new RegExp(`/${viewsDir}/`).test(normalized)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function importSpec(dirs, key) {
|
|
22
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
23
|
+
return `@/${base}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
meta: {
|
|
28
|
+
type: 'problem',
|
|
29
|
+
docs: {
|
|
30
|
+
description: '视图不许自己 create 模块实例,必须用 useXxxModule()',
|
|
31
|
+
recommended: true
|
|
32
|
+
},
|
|
33
|
+
schema: []
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
create(context) {
|
|
37
|
+
const dirs = getDirs(context)
|
|
38
|
+
const filename = context.filename
|
|
39
|
+
|
|
40
|
+
if (!isViewFile(filename, dirs)) return {}
|
|
41
|
+
|
|
42
|
+
const domainsBase = importSpec(dirs, 'domains')
|
|
43
|
+
// 匹配 @/domains/user 这种(模块入口)
|
|
44
|
+
const factoryPattern = new RegExp(`^${domainsBase}/[^/]+$`)
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
ImportDeclaration(node) {
|
|
48
|
+
const importPath = node.source.value
|
|
49
|
+
|
|
50
|
+
if (!factoryPattern.test(importPath)) return
|
|
51
|
+
|
|
52
|
+
// 看 import 的名字里有没有 createXxxModule
|
|
53
|
+
const importedNames = node.specifiers
|
|
54
|
+
.filter((spec) => spec.type === 'ImportSpecifier')
|
|
55
|
+
.map((spec) => spec.imported.name)
|
|
56
|
+
|
|
57
|
+
const factory = importedNames.find((name) => /^create[A-Z]\w*Module$/.test(name))
|
|
58
|
+
|
|
59
|
+
if (factory) {
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
message: `视图不许自己 create 模块实例,改用 useXxxModule()`
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIEW-01:视图不许直接 import 网络层、传输工具、执行器内部
|
|
3
|
+
* 视图只表达意图:动作走域中枢,数据直读状态层
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIRS = {
|
|
7
|
+
views: 'src/views',
|
|
8
|
+
api: 'src/api',
|
|
9
|
+
utils: 'src/utils',
|
|
10
|
+
domains: 'src/domains'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getDirs(context) {
|
|
14
|
+
return { ...DEFAULT_DIRS, ...(context.settings?.tha?.dirs || {}) }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isViewFile(filename, dirs) {
|
|
18
|
+
const viewsDir = dirs.views.replace(/\\/g, '/')
|
|
19
|
+
const normalized = filename.replace(/\\/g, '/')
|
|
20
|
+
return new RegExp(`/${viewsDir}/`).test(normalized)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function importSpec(dirs, key) {
|
|
24
|
+
const base = dirs[key].startsWith('src/') ? dirs[key].slice('src/'.length) : dirs[key]
|
|
25
|
+
return `@/${base}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
meta: {
|
|
30
|
+
type: 'problem',
|
|
31
|
+
docs: {
|
|
32
|
+
description: '视图不许直接 import 网络层、传输工具、执行器内部',
|
|
33
|
+
recommended: true
|
|
34
|
+
},
|
|
35
|
+
schema: []
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
create(context) {
|
|
39
|
+
const dirs = getDirs(context)
|
|
40
|
+
const filename = context.filename
|
|
41
|
+
|
|
42
|
+
if (!isViewFile(filename, dirs)) return {}
|
|
43
|
+
|
|
44
|
+
const apiBase = importSpec(dirs, 'api')
|
|
45
|
+
const utilsBase = importSpec(dirs, 'utils')
|
|
46
|
+
const domainsBase = importSpec(dirs, 'domains')
|
|
47
|
+
|
|
48
|
+
// 传输工具:request / stream / token
|
|
49
|
+
const transportUtils = new Set([
|
|
50
|
+
`${utilsBase}/request`,
|
|
51
|
+
`${utilsBase}/stream`,
|
|
52
|
+
`${utilsBase}/token`
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ImportDeclaration(node) {
|
|
57
|
+
const importPath = node.source.value
|
|
58
|
+
|
|
59
|
+
// import 了 api 层
|
|
60
|
+
if (importPath.startsWith(`${apiBase}/`)) {
|
|
61
|
+
context.report({
|
|
62
|
+
node,
|
|
63
|
+
message: '视图不许直接 import api 层,动作走域中枢'
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// import 了传输工具
|
|
69
|
+
if (transportUtils.has(importPath)) {
|
|
70
|
+
context.report({
|
|
71
|
+
node,
|
|
72
|
+
message: '视图不许直接 import 传输工具,动作走域中枢'
|
|
73
|
+
})
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// import 了执行器/零件内部
|
|
78
|
+
if (new RegExp(`^${domainsBase}/[^/]+/(executors|steps)/`).test(importPath)) {
|
|
79
|
+
context.report({
|
|
80
|
+
node,
|
|
81
|
+
message: '视图不许直接 import 执行器内部,走域中枢的动作'
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|