• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baidu / san-ssr / 3824729513

pending completion
3824729513

Pull #163

github

GitHub
Merge e05e440a2 into 5feaebca6
Pull Request #163: chore(deps): bump json5 and tsconfig-paths

1105 of 1107 branches covered (99.82%)

Branch coverage included in aggregate %.

1853 of 1854 relevant lines covered (99.95%)

4190.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/compilers/element-compiler.ts
1
/**
2
 * 编译一个 HTML 标签
3
 *
4
 * 对于 Element 类型的 ANode,采用这个编译器。
5
 * 大概就是先产出一个开始标签,再递归编译其内容,再产出一个结束标签。
6
 */
7
import { getANodePropByName } from '../ast/san-ast-util'
8
import { _ } from '../runtime/underscore'
9
import { IDGenerator } from '../utils/id-generator'
10
import { autoCloseTags } from '../utils/dom-util'
11
import { ANodeCompiler } from './anode-compiler'
12
import { ADirectiveBind, AElement, AProperty, BoolLiteral, Expr, NumberLiteral, StringLiteral } from 'san'
13
import { isExprNumberNode, isExprStringNode, isExprBoolNode, isExprWithValue } from '../ast/san-ast-type-guards'
14
import {
15
    createIfStrictEqual, createIfNotNull, createDefaultValue, createHTMLLiteralAppend, createHTMLExpressionAppend, NULL,
16
    L, I, ASSIGN, DEF, BINARY
17
} from '../ast/renderer-ast-util'
18
import { HelperCall, ArrayIncludes, Else, Foreach, If, MapLiteral } from '../ast/renderer-ast-dfn'
19
import { sanExpr, OutputType } from './san-expr-compiler'
20
import assert from 'assert'
21

22
const BOOL_ATTRIBUTES = ['readonly', 'disabled', 'multiple', 'checked']
15✔
23

24
/**
25
 * Element 的编译器
26
 *
27
 * 每个 ElementCompiler 对象对应于一个 ComponentClass,可以用来编译该组件中的所有 HTML 标签,并递归地调用 ANodeCompiler 来编译标签内容。
28
 */
29
export class ElementCompiler {
30
    /**
31
     * @param aNodeCompiler 编译 aNode 的对象,编译标签内容时用
32
     * @param id 抗冲突变量名产生器
33
     */
34
    constructor (
35
        private aNodeCompiler: ANodeCompiler,
36
        private id: IDGenerator
37
    ) {}
38

39
    /**
40
     * 编译元素标签头
41
     */
42
    * tagStart (aNode: AElement, dynamicTagName?: string) {
43
        const props = aNode.props
1,659✔
44
        const bindDirective = aNode.directives.bind
1,659✔
45
        const tagName = aNode.tagName!
1,659✔
46

47
        // element start '<'
48
        if (dynamicTagName) {
1,659✔
49
            yield createHTMLLiteralAppend('<')
19✔
50
            yield createHTMLExpressionAppend(I(dynamicTagName))
19✔
51
        } else if (tagName) {
1,640✔
52
            yield createHTMLLiteralAppend('<' + tagName)
1,633✔
53
        } else {
54
            yield createHTMLLiteralAppend('<')
7✔
55
            yield createHTMLExpressionAppend(I('tagName'))
7✔
56
        }
57

58
        // element properties
59
        const propsIndex = {}
1,659✔
60
        for (const prop of props) propsIndex[prop.name] = prop
2,738✔
61
        for (const prop of props) yield * this.compileProperty(tagName, prop, propsIndex)
2,738✔
62
        if (bindDirective) yield * this.compileBindProperties(tagName, bindDirective)
1,659✔
63

64
        // element end '>'
65
        yield createHTMLLiteralAppend('>')
1,659✔
66
    }
67

68
    private * compileProperty (tagName: string, prop: AProperty, propsIndex: { [key: string]: AProperty }) {
69
        if (prop.name === 'slot') return
2,738✔
70
        if (prop.name === 'value') {
2,665✔
71
            if (tagName === 'textarea') return
43✔
72
            if (tagName === 'select') {
40✔
73
                yield DEF('selectValue', sanExpr(prop.expr))
9✔
74
                yield createDefaultValue(I('selectValue'), L(''))
9✔
75
                return
9✔
76
            }
77
            if (tagName === 'option') {
31✔
78
                yield DEF('optionValue', sanExpr(prop.expr))
15✔
79
                yield createIfNotNull(I('optionValue'), [
15✔
80
                    createHTMLLiteralAppend(' value="'),
81
                    createHTMLExpressionAppend(I('optionValue')),
82
                    createHTMLLiteralAppend('"')
83
                ])
84
                yield createIfStrictEqual(I('optionValue'), I('selectValue'), [
15✔
85
                    createHTMLLiteralAppend(' selected')
86
                ])
87
                return
15✔
88
            }
89
        }
90

91
        if (prop.name === 'readonly' || prop.name === 'disabled' || prop.name === 'multiple') {
2,638✔
92
            if (this.isLiteral(prop.expr)) {
32✔
93
                if (_.boolAttrFilter(prop.name, prop.expr.value)) {
19✔
94
                    yield createHTMLLiteralAppend(` ${prop.name}`)
13✔
95
                }
96
            } else {
97
                yield createHTMLExpressionAppend(new HelperCall('boolAttrFilter', [L(prop.name), sanExpr(prop.expr)]))
13✔
98
            }
99
            return
32✔
100
        }
101

102
        const valueProp = propsIndex.value
2,606✔
103
        const inputType = propsIndex.type
2,606✔
104
        if (prop.name === 'checked' && tagName === 'input' && valueProp && inputType) {
2,606✔
105
            assert(isExprWithValue(inputType.expr))
7✔
106
            switch (inputType.expr.value) {
7✔
107
            case 'checkbox':
108
                yield new If(
3✔
109
                    new ArrayIncludes(sanExpr(prop.expr), sanExpr(valueProp.expr)),
110
                    [createHTMLLiteralAppend(' checked')]
111
                )
112
                return
3✔
113
            case 'radio':
114
                yield createIfStrictEqual(sanExpr(prop.expr), sanExpr(valueProp.expr), [
3✔
115
                    createHTMLLiteralAppend(' checked')
116
                ])
117
                return
3✔
118
            }
119
        }
120
        if (this.isLiteral(prop.expr)) {
2,600✔
121
            yield createHTMLLiteralAppend(_.attrFilter(prop.name, prop.expr.value, true))
93✔
122
        } else {
123
            yield createHTMLExpressionAppend(
2,507✔
124
                new HelperCall('attrFilter', [L(prop.name), sanExpr(prop.expr, OutputType.ESCAPE), L(false)])
125
            )
126
        }
127
    }
128

129
    private isLiteral (expr: Expr): expr is BoolLiteral | StringLiteral | NumberLiteral {
130
        return isExprBoolNode(expr) || isExprStringNode(expr) || isExprNumberNode(expr)
2,632✔
131
    }
132

133
    private * compileBindProperties (tagName: string, bindDirective: ADirectiveBind) {
134
        const bindProps = this.id.next('bindProps')
9✔
135
        yield DEF(bindProps, BINARY(sanExpr(bindDirective.value), '||', new MapLiteral([])))
9✔
136

137
        const key = I('key')
9✔
138
        const value = I('value')
9✔
139
        const iterable = I(bindProps)
9✔
140
        yield new Foreach(key, value, iterable, [
9✔
141
            new If(new ArrayIncludes(L(BOOL_ATTRIBUTES), key), [
142
                createHTMLExpressionAppend(new HelperCall('boolAttrFilter', [key, value]))
143
            ]),
144
            new Else([
145
                createHTMLExpressionAppend(new HelperCall('attrFilter', [key, value, L(true)]))
146
            ])
147
        ])
148
    }
149

150
    /**
151
     * 编译元素闭合
152
     */
153
    * tagEnd (aNode: AElement, dynamicTagName?: string) {
154
        const tagName = aNode.tagName
1,654✔
155

156
        if (dynamicTagName) {
1,654✔
157
            yield createHTMLLiteralAppend('</')
19✔
158
            yield createHTMLExpressionAppend(I(dynamicTagName))
19✔
159
            yield createHTMLLiteralAppend('>')
19✔
160
        } else if (tagName) {
1,635✔
161
            if (!autoCloseTags.has(tagName)) {
1,628✔
162
                yield createHTMLLiteralAppend('</' + tagName + '>')
1,614✔
163
            }
164
            if (tagName === 'select') {
1,628✔
165
                yield ASSIGN(I('selectValue'), NULL)
9✔
166
            }
167
            if (tagName === 'option') {
1,628✔
168
                yield ASSIGN(I('optionValue'), NULL)
18✔
169
            }
170
        } else {
171
            yield createHTMLLiteralAppend('</')
7✔
172
            yield createHTMLExpressionAppend(I('tagName'))
7✔
173
            yield createHTMLLiteralAppend('>')
7✔
174
        }
175
    }
176

177
    /**
178
     * 编译元素内容
179
     */
180
    * inner (aNode: AElement) {
181
        // inner content
182
        if (aNode.tagName === 'textarea') {
1,719✔
183
            const valueProp = getANodePropByName(aNode, 'value')
4✔
184
            if (valueProp) yield createHTMLExpressionAppend(sanExpr(valueProp.expr, OutputType.ESCAPE_HTML))
4✔
185
            return
4✔
186
        }
187

188
        const htmlDirective = aNode.directives.html
1,715✔
189
        if (htmlDirective) {
1,715✔
190
            yield createHTMLExpressionAppend(sanExpr(htmlDirective.value, OutputType.HTML))
9✔
191
            return
9✔
192
        }
193
        // 只有 ATextNode 没有 children 属性,它的编译走了 ANodeCompiler#compileText(),不会进入这里
194
        for (const aNodeChild of aNode.children!) yield * this.aNodeCompiler.compile(aNodeChild, false)
2,130✔
195
    }
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc