-
Notifications
You must be signed in to change notification settings - Fork 0
/
no-duplicated-vue-import.ts
118 lines (109 loc) · 4 KB
/
no-duplicated-vue-import.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { LogContexts, LogLevels } from 'bs-logger'
import type { ConfigSet } from 'ts-jest/dist/config/config-set';
import type * as _ts from 'typescript'
// this is a unique identifier for your transformer. `ts-jest` uses this for jest cache key
export const name = 'no-duplicated-vue-import'
// increment this each time you change the behavior of your transformer. `ts-jest` uses this for jest cache key
export const version = 1
const VUE_GLOBAL_NAME = 'vue'
const ROOT_LEVEL_AST = 1
/**
* @usage In jest config, define as
* // jest.config.js
* module.exports = {
* // other configs
* globals: {
* 'ts-jest': {
* astTransformers: {
* after: ['vue-ts-jest/dist/transformers/vue-no-duplicated-import']
* }
* }
* }
* }
*/
export function factory(cs: ConfigSet): (ctx: _ts.TransformationContext) => _ts.Transformer<_ts.SourceFile> {
const logger = cs.logger.child({ namespace: 'vue-no-duplicated-import' })
const ts = cs.compilerModule
const isRequire = (node: _ts.Node): node is _ts.CallExpression =>
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'require' &&
ts.isStringLiteral(node.arguments[0]) &&
node.arguments.length === 1
const isDuplicatedGlobalVueImport = (node: _ts.Node) => ts.isVariableStatement(node)
&& node.declarationList.declarations[0].initializer
&& isRequire(node.declarationList.declarations[0].initializer)
&& (node.declarationList.declarations[0].initializer.arguments[0] as _ts.StringLiteral).text === VUE_GLOBAL_NAME
&& ts.isIdentifier(node.declarationList.declarations[0].name)
/**
* Handle import declaration import { defineComponent, ref } from 'vue' which doesn't generate Identifier text when
* this transformer is executed
*/
&& !node.declarationList.declarations[0].name.text
const createVisitor = (ctx: _ts.TransformationContext, _sf: _ts.SourceFile) => {
/**
* Current block level
*/
let level = 0
/**
* List of nodes which needs to be deleted, indexed by their owning level
*/
const toBeDeletedNodes: _ts.Statement[][] = []
/**
* Called when we enter a block to increase the level
*/
const enter = () => {
level++
// reuse arrays
if (toBeDeletedNodes[level]) {
toBeDeletedNodes[level].splice(0, toBeDeletedNodes[level].length)
}
}
/**
* Called when we leave a block to decrease the level
*/
const exit = () => level--
/**
* Adds a node to the list of nodes to be deleted in the current level
*/
const gatherDeleteNodes = (node: _ts.Statement) => {
if (toBeDeletedNodes[level]) {
toBeDeletedNodes[level].push(node)
} else {
toBeDeletedNodes[level] = [node]
}
}
const visitor: _ts.Visitor = node => {
// enter this level
enter()
// visit each child
let resultNode = ts.visitEachChild(node, visitor, ctx)
// check if we have something to delete in this level
if (toBeDeletedNodes[level]?.length) {
const newNode = ts.getMutableClone(resultNode) as _ts.Block
const otherStmts = (resultNode as _ts.Block).statements.filter(
(s) => !toBeDeletedNodes[level].includes(s) && !isDuplicatedGlobalVueImport(s),
)
resultNode = {
...newNode,
statements: ts.createNodeArray([...otherStmts]),
} as _ts.Statement
}
// exit the level
exit()
if (isDuplicatedGlobalVueImport(resultNode) && level === ROOT_LEVEL_AST) {
gatherDeleteNodes(resultNode as _ts.Statement)
}
// finally returns the currently visited node
return resultNode
}
return visitor
}
// returns the transformer factory
return (ctx: _ts.TransformationContext): _ts.Transformer<_ts.SourceFile> =>
logger.wrap(
{ [LogContexts.logLevel]: LogLevels.debug, call: null },
'visitSourceFileNode(): vue no duplicated import',
(sf: _ts.SourceFile) => ts.visitNode(sf, createVisitor(ctx, sf)),
)
}