/)) {
157 | const rawMark: RawMark = { ...mark, code: MarkSideType.LEFT }
158 | unresolvedCodeMarks.push(rawMark)
159 | marks.push(rawMark)
160 | return
161 | } else if (mark.startValue.match(/<\/code.*>/)) {
162 | const rawMark: RawMark = { ...mark, code: MarkSideType.RIGHT }
163 | const leftCode = unresolvedCodeMarks.pop()
164 | if (leftCode) {
165 | leftCode.rightPair = rawMark
166 | }
167 | marks.push(rawMark)
168 | return
169 | }
170 | marks.push(mark)
171 | } else {
172 | const firstChild = inline.children[0]
173 | const lastChild = inline.children[inline.children.length - 1]
174 | if (!firstChild.position || !lastChild.position) {
175 | return
176 | }
177 | const innerStartOffset = firstChild.position.start.offset || 0
178 | const innerEndOffset = lastChild.position.end.offset || 0
179 | const mark: Mark = {
180 | type: MarkType.HYPER,
181 | // TODO: typeof RawMark.meta
182 | meta: inline.type,
183 | startIndex: startOffset - offset,
184 | startValue: str.substring(startOffset, innerStartOffset),
185 | endIndex: innerEndOffset - offset,
186 | endValue: str.substring(innerEndOffset, endOffset)
187 | }
188 | marks.push(mark)
189 | }
190 | })
191 |
192 | blockMark.value = str.substring(
193 | block.position.start.offset || 0,
194 | block.position.end.offset || 0
195 | )
196 |
197 | blockMark.hyperMarks = marks
198 | .map((mark) => {
199 | if (isRawMark(mark)) {
200 | if (mark.code === MarkSideType.RIGHT) {
201 | return
202 | }
203 | if (mark.code === MarkSideType.LEFT) {
204 | const { rightPair } = mark
205 | mark.startValue = str.substring(
206 | mark.startIndex + offset,
207 | mark.endIndex + offset
208 | )
209 | mark.endIndex = rightPair?.endIndex || 0
210 | mark.endValue = ''
211 | delete mark.rightPair
212 | }
213 | }
214 | return mark
215 | })
216 | .filter(Boolean) as Mark[]
217 | }
218 |
219 | /**
220 | - travel all blocks/lists/tables/rows/cells
221 | - content: paragraph/heading/table-cell
222 | - no content: thematic break/code/html
223 | - for all phrasings:
224 | - no text: inline code/break/image/image ref/footnote ref/html
225 | - marks: emphasis/strong/delete/footnote/link/link ref
226 | */
227 | const parser = (data: ParsedStatus): ParsedStatus => {
228 | const value = data.value
229 | const modifiedValue = data.modifiedValue
230 | const ignoredByParsers = data.ignoredByParsers
231 |
232 | const blockMarks: BlockMark[] = []
233 |
234 | const tree: Ast.Root = unified()
235 | .use(markdown)
236 | .use(gfm)
237 | .use(frontmatter)
238 | .parse(modifiedValue) as Ast.Root
239 |
240 | // - travel and record all paragraphs/headings/table-cells into blocks
241 | // - for each block, travel and record all
242 | // - - 'hyper' marks: emphasis/strong/delete/footnote/link/linkRef and continue
243 | // - - 'raw' marks: inlineCode/break/image/imageRef/footnoteRef/html and stop
244 | travelBlocks(tree, blockMarks)
245 |
246 | // for each block marks
247 | // - get block.start.offset
248 | // - for each marks
249 | // - - startIndex: mark.start.offset - offset
250 | // - - startValue: [mark.start.offset - offset, mark.firstChild.start.offset - offset]
251 | // - - endIndex: mark.lastChild.end.offset - offset
252 | // - - endValue: [mark.lastChild.end.offset - offset, mark.end.offset]
253 | blockMarks.forEach((blockMark) => processBlockMark(blockMark, value))
254 | data.blocks = blockMarks.map((b): Block => {
255 | const position = parsePosition(b.block.position)
256 | ignoredByParsers.forEach(({ index, length, originValue: raw, meta }) => {
257 | if (position.start <= index && position.end >= index + length) {
258 | if (b.hyperMarks) {
259 | b.hyperMarks.push({
260 | type: MarkType.RAW,
261 | meta,
262 | startIndex: index - position.start,
263 | startValue: raw,
264 | endIndex: index - position.start + length,
265 | endValue: ''
266 | })
267 | }
268 | }
269 | })
270 | return {
271 | value: b.value || '',
272 | marks: b.hyperMarks || [],
273 | ...position
274 | }
275 | })
276 | data.ignoredByParsers = []
277 | return data
278 | }
279 |
280 | export default parser
281 |
--------------------------------------------------------------------------------
/src/rules/space-bracket.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview
3 | *
4 | * This rule is checking spaces besides brackets.
5 | *
6 | * Options
7 | * - noSpaceInsideBracket: boolean | undefined
8 | * - spaceOutsideHalfBracket: boolean | undefined
9 | * - nospaceOutsideFullBracket: boolean | undefined
10 | *
11 | * Details:
12 | * - noSpaceInsideBracket:
13 | * - left-bracket x anything
14 | * - non-left-bracket x right-bracket
15 | * - spaceOutsideHalfBracket:
16 | * - right-half-bracket x left-half-bracket
17 | * - right-half-bracket x content/left-quotation/code
18 | * - content/right-quotation/code x left-half-bracket
19 | * - noSpaceOutsideFullBracket:
20 | * - right-full-bracket x left-full-bracket
21 | * - right-full-bracket x content/left-quotation/code
22 | * - content/right-quotation/code x left-full-bracket
23 | */
24 |
25 | import {
26 | CharType,
27 | GroupTokenType,
28 | Handler,
29 | isLetterType,
30 | isFullwidthPair,
31 | MarkSideType,
32 | MutableGroupToken,
33 | MutableSingleToken,
34 | MutableToken,
35 | HyperTokenType
36 | } from '../parser/index.js'
37 | import {
38 | checkSpaceAfter,
39 | findVisibleTokenAfter,
40 | findVisibleTokenBefore,
41 | findWrappersBetween,
42 | findTokenAfter,
43 | findTokenBefore,
44 | Options
45 | } from './util.js'
46 | import {
47 | BRACKET_NOSPACE_INSIDE,
48 | BRACKET_NOSPACE_OUTSIDE,
49 | BRACKET_SPACE_OUTSIDE
50 | } from './messages.js'
51 |
52 | const isFullWidth = (char: string, adjusted: string): boolean => {
53 | return isFullwidthPair(char) && adjusted.indexOf(char) === -1
54 | }
55 |
56 | const shouldSkip = (
57 | before: MutableToken | undefined,
58 | beforeTokenSeq: MutableToken[],
59 | token: MutableSingleToken,
60 | afterTokenSeq: MutableToken[],
61 | after: MutableToken | undefined
62 | ): boolean => {
63 | if (!before || !after) {
64 | return false
65 | }
66 | if (isFullwidthPair(token.value) || isFullwidthPair(token.modifiedValue)) {
67 | return false
68 | }
69 | if (
70 | beforeTokenSeq.filter((x) => x.spaceAfter).length ||
71 | afterTokenSeq.filter((x) => x.spaceAfter).length
72 | ) {
73 | return false
74 | }
75 | return (
76 | // x(x
77 | // ^
78 | (before.type === CharType.WESTERN_LETTER ||
79 | // x()
80 | // ^
81 | (before.value === '(' && token.value === ')')) &&
82 | // x)x
83 | // ^
84 | (after.type === CharType.WESTERN_LETTER ||
85 | // ()x
86 | // ^
87 | (token.value === '(' && after.value === ')'))
88 | )
89 | }
90 |
91 | const generateHandler = (options: Options): Handler => {
92 | const noInsideBracketOption = options.noSpaceInsideBracket
93 | const spaceOutsideHalfBracketOption = options.spaceOutsideHalfwidthBracket
94 | const noSpaceOutsideFullBracketOption = options.noSpaceOutsideFullwidthBracket
95 | const adjustedFullWidthOption = options.adjustedFullwidthPunctuation || ''
96 |
97 | return (token: MutableToken, _: number, group: MutableGroupToken) => {
98 | // skip non-bracket tokens
99 | if (token.type !== HyperTokenType.BRACKET_MARK) {
100 | return
101 | }
102 |
103 | // 1. no space inside bracket
104 | if (noInsideBracketOption) {
105 | if (token.markSide === MarkSideType.LEFT) {
106 | // no space after
107 | const tokenAfter = findTokenAfter(group, token)
108 | if (tokenAfter) {
109 | checkSpaceAfter(token, '', BRACKET_NOSPACE_INSIDE)
110 | }
111 | } else {
112 | // no space before
113 | const tokenBefore = findTokenBefore(group, token)
114 | if (
115 | tokenBefore &&
116 | // dedupe
117 | tokenBefore.markSide !== MarkSideType.LEFT
118 | ) {
119 | checkSpaceAfter(tokenBefore, '', BRACKET_NOSPACE_INSIDE)
120 | }
121 | }
122 | }
123 |
124 | // skip bracket between half-width content without spaces
125 | // or empty brackets beside half-width content without spaces
126 | const contentTokenBefore = findVisibleTokenBefore(group, token)
127 | const contentTokenAfter = findVisibleTokenAfter(group, token)
128 | const { spaceHost: beforeSpaceHost, tokens: beforeTokenSeq } =
129 | findWrappersBetween(group, contentTokenBefore, token)
130 | const { spaceHost: afterSpaceHost, tokens: afterTokenSeq } =
131 | findWrappersBetween(group, token, contentTokenAfter)
132 | if (
133 | shouldSkip(
134 | contentTokenBefore,
135 | beforeTokenSeq,
136 | token,
137 | afterTokenSeq,
138 | contentTokenAfter
139 | )
140 | ) {
141 | return
142 | }
143 |
144 | // 2. spaces outside half/full bracket
145 | if (
146 | typeof spaceOutsideHalfBracketOption !== 'undefined' ||
147 | noSpaceOutsideFullBracketOption
148 | ) {
149 | const fullWidth = isFullWidth(
150 | token.modifiedValue,
151 | adjustedFullWidthOption
152 | )
153 |
154 | // 2.1 right-bracket x left-bracket
155 | if (contentTokenAfter) {
156 | if (
157 | token.markSide === MarkSideType.RIGHT &&
158 | contentTokenAfter.markSide === MarkSideType.LEFT
159 | ) {
160 | if (afterSpaceHost) {
161 | const hasFullWidth =
162 | fullWidth ||
163 | isFullWidth(
164 | contentTokenAfter.modifiedValue,
165 | adjustedFullWidthOption
166 | )
167 |
168 | // 2.1.1 any-full-bracket
169 | // 2.1.2 right-half-bracket x left-half-bracket
170 | if (hasFullWidth) {
171 | if (noSpaceOutsideFullBracketOption) {
172 | checkSpaceAfter(token, '', BRACKET_NOSPACE_OUTSIDE)
173 | }
174 | } else {
175 | // skip no spaces between
176 | if (afterTokenSeq.filter((x) => x.spaceAfter).length > 0) {
177 | if (typeof spaceOutsideHalfBracketOption !== 'undefined') {
178 | const spaceAfter = spaceOutsideHalfBracketOption ? ' ' : ''
179 | const message = spaceOutsideHalfBracketOption
180 | ? BRACKET_SPACE_OUTSIDE
181 | : BRACKET_NOSPACE_OUTSIDE
182 | checkSpaceAfter(token, spaceAfter, message)
183 | }
184 | }
185 | }
186 | }
187 | }
188 | }
189 |
190 | // 2.2 content/right-quotation/code x left-bracket
191 | // 2.3 right-racket x content/left-quotation/code
192 | if (token.markSide === MarkSideType.LEFT) {
193 | if (
194 | contentTokenBefore &&
195 | (isLetterType(contentTokenBefore.type) ||
196 | contentTokenBefore.type === GroupTokenType.GROUP ||
197 | contentTokenBefore.type === HyperTokenType.CODE_CONTENT)
198 | ) {
199 | if (beforeSpaceHost) {
200 | // 2.2.1 content/right-quotation/code x left-full-bracket
201 | // 2.2.2 content/right-quotation/code x left-half-bracket
202 | if (
203 | fullWidth ||
204 | (contentTokenBefore.type === GroupTokenType.GROUP &&
205 | isFullWidth(
206 | contentTokenBefore.modifiedEndValue,
207 | adjustedFullWidthOption
208 | ))
209 | ) {
210 | if (noSpaceOutsideFullBracketOption) {
211 | checkSpaceAfter(beforeSpaceHost, '', BRACKET_NOSPACE_OUTSIDE)
212 | }
213 | } else {
214 | if (typeof spaceOutsideHalfBracketOption !== 'undefined') {
215 | const spaceAfter = spaceOutsideHalfBracketOption ? ' ' : ''
216 | const message = spaceOutsideHalfBracketOption
217 | ? BRACKET_SPACE_OUTSIDE
218 | : BRACKET_NOSPACE_OUTSIDE
219 | checkSpaceAfter(beforeSpaceHost, spaceAfter, message)
220 | }
221 | }
222 | }
223 | }
224 | } else {
225 | if (
226 | contentTokenAfter &&
227 | (isLetterType(contentTokenAfter.type) ||
228 | contentTokenAfter.type === GroupTokenType.GROUP ||
229 | contentTokenAfter.type === HyperTokenType.CODE_CONTENT)
230 | ) {
231 | if (afterSpaceHost) {
232 | // 2.3.1 right-full-bracket x content/left-quotation/code
233 | // 2.4.2 right-half-bracket x content/left-quotation/code
234 | if (
235 | fullWidth ||
236 | (contentTokenAfter.type === GroupTokenType.GROUP &&
237 | isFullWidth(
238 | contentTokenAfter.modifiedStartValue,
239 | adjustedFullWidthOption
240 | ))
241 | ) {
242 | if (noSpaceOutsideFullBracketOption) {
243 | checkSpaceAfter(afterSpaceHost, '', BRACKET_NOSPACE_OUTSIDE)
244 | }
245 | } else {
246 | if (typeof spaceOutsideHalfBracketOption !== 'undefined') {
247 | const spaceAfter = spaceOutsideHalfBracketOption ? ' ' : ''
248 | const message = spaceOutsideHalfBracketOption
249 | ? BRACKET_SPACE_OUTSIDE
250 | : BRACKET_NOSPACE_OUTSIDE
251 | checkSpaceAfter(afterSpaceHost, spaceAfter, message)
252 | }
253 | }
254 | }
255 | }
256 | }
257 | }
258 | }
259 | }
260 |
261 | export const defaultConfig: Options = {
262 | spaceOutsideHalfBracket: true,
263 | noSpaceInsideBracket: true
264 | }
265 |
266 | export default generateHandler
267 |
--------------------------------------------------------------------------------
/test/example-article.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 介绍
3 | type: guide
4 | order: 2
5 | ---
6 |
7 | ## Vue.js 是什么
8 |
9 |
10 |
11 | Vue (读音 /vjuː/,类似于 **view**) 是一套用于构建用户界面的**渐进式框架**。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与[现代化的工具链](single-file-components.html)以及各种[支持类库](https://github.com/vuejs/awesome-vue#libraries--plugins)结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
12 |
13 | 如果你想在深入学习 Vue 之前对它有更多了解,我们制作了一个视频,带您了解其核心概念和一个示例工程。
14 |
15 |
16 | 如果你已经是有经验的前端开发者,想知道 Vue 与其它库/框架有哪些区别,请查看[对比其它框架](comparison.html)。
17 |
18 | ## 起步
19 |
20 |
21 |
22 | 官方指南假设你已了解关于 HTML、CSS 和 JavaScript 的中级知识。如果你刚开始学习前端开发,将框架作为你的第一步可能不是最好的主意——掌握好基础知识再来吧!之前有其它框架的使用经验会有帮助,但这不是必需的。
23 |
24 | 安装
25 |
26 | 尝试 Vue.js 最简单的方法是使用 [JSFiddle 上的 Hello World 例子](https://jsfiddle.net/chrisvfritz/50wL7mdz/)。你可以在浏览器新标签页中打开它,跟着例子学习一些基础用法。或者你也可以创建一个 .html 文件,然后通过如下方式引入 Vue:
27 |
28 | ``` html
29 |
30 |
31 | ```
32 |
33 | 或者:
34 |
35 | ``` html
36 |
37 |
38 | ```
39 |
40 | [安装教程](/guide/installation.html)给出了更多安装 Vue 的方式。请注意我们**不推荐**新手直接使用 `vue-cli`,尤其是在你还不熟悉基于 Node.js 的构建工具时。
41 |
42 | 如果你喜欢交互式的东西,你也可以查阅[这个 Scrimba 上的系列教程](https://scrimba.com/g/gvuedocs),它揉合了录屏和代码试验田,并允许你随时暂停和播放。
43 |
44 | ## 声明式渲染
45 |
46 |
47 |
48 | Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
49 |
50 | ``` html
51 |
52 | {{ message }}
53 |
54 | ```
55 | ``` js
56 | var app = new Vue({
57 | el: '#app',
58 | data: {
59 | message: 'Hello Vue!'
60 | }
61 | })
62 | ```
63 | {% raw %}
64 |
65 | {{ message }}
66 |
67 |
75 | {% endraw %}
76 |
77 | 我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是**响应式的**。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 `app.message` 的值,你将看到上例相应地更新。
78 |
79 | 除了文本插值,我们还可以像这样来绑定元素特性:
80 |
81 | ``` html
82 |
83 |
84 | 鼠标悬停几秒钟查看此处动态绑定的提示信息!
85 |
86 |
87 | ```
88 | ``` js
89 | var app2 = new Vue({
90 | el: '#app-2',
91 | data: {
92 | message: '页面加载于 ' + new Date().toLocaleString()
93 | }
94 | })
95 | ```
96 | {% raw %}
97 |
98 |
99 | 鼠标悬停几秒钟查看此处动态绑定的提示信息!
100 |
101 |
102 |
110 | {% endraw %}
111 |
112 | 这里我们遇到了一点新东西。你看到的 `v-bind` 特性被称为**指令**。指令带有前缀 `v-`,以表示它们是 Vue 提供的特殊特性。可能你已经猜到了,它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 `title` 特性和 Vue 实例的 `message` 属性保持一致”。
113 |
114 | 如果你再次打开浏览器的 JavaScript 控制台,输入 `app2.message = '新消息'`,就会再一次看到这个绑定了 `title` 特性的 HTML 已经进行了更新。
115 |
116 | ## 条件与循环
117 |
118 |
119 |
120 | 控制切换一个元素是否显示也相当简单:
121 |
122 | ``` html
123 |
126 | ```
127 | ``` js
128 | var app3 = new Vue({
129 | el: '#app-3',
130 | data: {
131 | seen: true
132 | }
133 | })
134 | ```
135 | {% raw %}
136 |
137 | 现在你看到我了
138 |
139 |
147 | {% endraw %}
148 |
149 | 继续在控制台输入 `app3.seen = false`,你会发现之前显示的消息消失了。
150 |
151 | 这个例子演示了我们不仅可以把数据绑定到 DOM 文本或特性,还可以绑定到 DOM **结构**。此外,Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用[过渡效果](transitions.html)。
152 |
153 | 还有其它很多指令,每个都有特殊的功能。例如,`v-for` 指令可以绑定数组的数据来渲染一个项目列表:
154 |
155 | ``` html
156 |
157 |
158 | -
159 | {{ todo.text }}
160 |
161 |
162 |
163 | ```
164 | ``` js
165 | var app4 = new Vue({
166 | el: '#app-4',
167 | data: {
168 | todos: [
169 | { text: '学习 JavaScript' },
170 | { text: '学习 Vue' },
171 | { text: '整个牛项目' }
172 | ]
173 | }
174 | })
175 | ```
176 | {% raw %}
177 |
178 |
179 | -
180 | {{ todo.text }}
181 |
182 |
183 |
184 |
196 | {% endraw %}
197 |
198 | 在控制台里,输入 `app4.todos.push({ text: '新项目' })`,你会发现列表最后添加了一个新项目。
199 |
200 | ## 处理用户输入
201 |
202 |
203 |
204 | 为了让用户和你的应用进行交互,我们可以用 `v-on` 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:
205 |
206 | ``` html
207 |
208 |
{{ message }}
209 |
210 |
211 | ```
212 | ``` js
213 | var app5 = new Vue({
214 | el: '#app-5',
215 | data: {
216 | message: 'Hello Vue.js!'
217 | },
218 | methods: {
219 | reverseMessage: function () {
220 | this.message = this.message.split('').reverse().join('')
221 | }
222 | }
223 | })
224 | ```
225 | {% raw %}
226 |
227 |
{{ message }}
228 |
229 |
230 |
243 | {% endraw %}
244 |
245 | 注意在 `reverseMessage` 方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。
246 |
247 | Vue 还提供了 `v-model` 指令,它能轻松实现表单输入和应用状态之间的双向绑定。
248 |
249 | ``` html
250 |
251 |
{{ message }}
252 |
253 |
254 | ```
255 | ``` js
256 | var app6 = new Vue({
257 | el: '#app-6',
258 | data: {
259 | message: 'Hello Vue!'
260 | }
261 | })
262 | ```
263 | {% raw %}
264 |
265 |
{{ message }}
266 |
267 |
268 |
276 | {% endraw %}
277 |
278 | ## 组件化应用构建
279 |
280 |
281 |
282 | 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
283 |
284 | 
285 |
286 | 在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
287 |
288 | ``` js
289 | // 定义名为 todo-item 的新组件
290 | Vue.component('todo-item', {
291 | template: '这是个待办项'
292 | })
293 |
294 | var app = new Vue(...)
295 | ```
296 |
297 | 现在你可以用它构建另一个组件模板:
298 |
299 | ``` html
300 |
301 |
302 |
303 |
304 | ```
305 |
306 | 但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 [prop](components.html#通过-Prop-向子组件传递数据):
307 |
308 | ``` js
309 | Vue.component('todo-item', {
310 | // todo-item 组件现在接受一个
311 | // "prop",类似于一个自定义特性。
312 | // 这个 prop 名为 todo。
313 | props: ['todo'],
314 | template: '{{ todo.text }}'
315 | })
316 | ```
317 |
318 | 现在,我们可以使用 `v-bind` 指令将待办项传到循环输出的每个组件中:
319 |
320 | ``` html
321 |
322 |
323 |
329 |
334 |
335 |
336 | ```
337 |
338 | ``` js
339 | Vue.component('todo-item', {
340 | props: ['todo'],
341 | template: '{{ todo.text }}'
342 | })
343 |
344 | var app7 = new Vue({
345 | el: '#app-7',
346 | data: {
347 | groceryList: [
348 | { id: 0, text: '蔬菜' },
349 | { id: 1, text: '奶酪' },
350 | { id: 2, text: '随便其它什么人吃的东西' }
351 | ]
352 | }
353 | })
354 | ```
355 | {% raw %}
356 |
361 |
377 | {% endraw %}
378 |
379 | 尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进 `` 组件,提供更为复杂的模板和逻辑,而不会影响到父单元。
380 |
381 | 在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在[后续教程](components.html)中我们将详述组件,不过这里有一个 (假想的) 例子,以展示使用了组件的应用模板是什么样的:
382 |
383 | ``` html
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 | ```
392 |
393 | ### 与自定义元素的关系
394 |
395 | 你可能已经注意到 Vue 组件非常类似于**自定义元素**——它是 [Web 组件规范](https://www.w3.org/wiki/WebComponents/)的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 [Slot API](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md) 与 `is` 特性。但是,还是有几个关键差别:
396 |
397 | 1. Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
398 |
399 | 2. Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。
400 |
401 | 虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,[依然有很好的互操作性](https://custom-elements-everywhere.com/#vue)。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
402 |
403 | ## 准备好了吗?
404 |
405 | 我们刚才简单介绍了 Vue 核心最基本的功能——本教程的其余部分将更加详细地涵盖这些功能以及其它高级功能,所以请务必读完整个教程!
406 |
407 |
408 |
--------------------------------------------------------------------------------
/test/md.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, test, expect } from 'vitest'
2 |
3 | import run from '../src/run.js'
4 | import markdownParser from '../src/hypers/md.js'
5 | import { ParsedStatus } from '../src/hypers/types.js'
6 | import { options } from './prepare.js'
7 |
8 | const getOutput = (str: string) => run(str, options).result
9 |
10 | describe('parser with markdown', () => {
11 | test('[md parser] single paragraph', () => {
12 | const text = 'X [xxx](xxx) X *y* __x__ `ss` _0_ ~~asd~~ *asf**asf**adsf*'
13 | const data: ParsedStatus = {
14 | value: text,
15 | modifiedValue: text,
16 | ignoredByRules: [],
17 | ignoredByParsers: [],
18 | blocks: [
19 | {
20 | value: text,
21 | marks: [],
22 | start: 0,
23 | end: text.length - 1
24 | }
25 | ]
26 | }
27 | const result = markdownParser(data).blocks
28 | const marks = [
29 | {
30 | type: 'hyper',
31 | meta: 'link',
32 | startIndex: 2,
33 | startValue: '[',
34 | endIndex: 6,
35 | endValue: '](xxx)'
36 | },
37 | {
38 | type: 'hyper',
39 | meta: 'emphasis',
40 | startIndex: 15,
41 | startValue: '*',
42 | endIndex: 17,
43 | endValue: '*'
44 | },
45 | {
46 | type: 'hyper',
47 | meta: 'strong',
48 | startIndex: 19,
49 | startValue: '__',
50 | endIndex: 22,
51 | endValue: '__'
52 | },
53 | {
54 | type: 'raw',
55 | meta: 'inlineCode',
56 | startIndex: 25,
57 | endIndex: 29,
58 | startValue: '`ss`',
59 | endValue: ''
60 | },
61 | {
62 | type: 'hyper',
63 | meta: 'emphasis',
64 | startIndex: 30,
65 | startValue: '_',
66 | endIndex: 32,
67 | endValue: '_'
68 | },
69 | {
70 | type: 'hyper',
71 | meta: 'delete',
72 | startIndex: 34,
73 | startValue: '~~',
74 | endIndex: 39,
75 | endValue: '~~'
76 | },
77 | {
78 | type: 'hyper',
79 | meta: 'emphasis',
80 | startIndex: 42,
81 | startValue: '*',
82 | endIndex: 57,
83 | endValue: '*'
84 | },
85 | {
86 | type: 'hyper',
87 | meta: 'strong',
88 | startIndex: 46,
89 | startValue: '**',
90 | endIndex: 51,
91 | endValue: '**'
92 | }
93 | ]
94 | expect(result.length).toBe(1)
95 | expect(result[0].value).toBe(text)
96 | expect(result[0].marks).toEqual(marks)
97 | })
98 | })
99 |
100 | describe('markdown lint', () => {
101 | test('[md] single paragraph', () => {
102 | expect(getOutput('中文 X[ xxx ](xxx)X`hello`world')).toBe(
103 | '中文 X [xxx](xxx) X `hello` world'
104 | )
105 | })
106 | test('[md] frontmatter', () => {
107 | expect(
108 | getOutput('---\ntitle: 介绍\ntype: guide\norder: 2\n---\n## Vue 是什么\n')
109 | ).toBe('---\ntitle: 介绍\ntype: guide\norder: 2\n---\n## Vue 是什么\n')
110 | })
111 | test('[md] space between raw content', () => {
112 | // 我们 制作了一个视频
113 | expect(
114 | getOutput('我们制作了一个视频')
115 | ).toBe('我们制作了一个视频')
116 | })
117 | test('[md] space between raw content 2', () => {
118 | // 我们 制作了一个视频
119 | expect(
120 | getOutput('Hello制作了一个视频World')
121 | ).toBe('Hello 制作了一个视频 World')
122 | })
123 | test('[md] space between raw content 3', () => {
124 | // 创建一个 。 html 文件
125 | expect(getOutput('创建一个 .html 文件')).toBe(
126 | '创建一个 .html 文件'
127 | )
128 | })
129 | test('[md] raw content', () => {
130 | // {% raw %}...
{% raw %}
131 | expect(
132 | getOutput(
133 | '中文 {% raw %}\n...
\n{% raw %}'
134 | )
135 | ).toBe('中文 {% raw %}\n...
\n{% raw %}')
136 | })
137 | test('[md] empty lines', () => {
138 | expect(getOutput('中文 a\n\nb\n\nc')).toBe('中文 a\n\nb\n\nc')
139 | })
140 | test('[md] inline code', () => {
141 | expect(getOutput(`改进 \`\` 组件`)).toBe(
142 | `改进 \`\` 组件`
143 | )
144 | })
145 | test('[md] footnote + inline code at the end', () => {
146 | expect(
147 | getOutput(
148 | '这样写将始终添加 `errorClass`,但是只有在 `isActive` 是 truthy[[1]](#footnote-1) 时才添加 `activeClass`。'
149 | )
150 | ).toBe(
151 | '这样写将始终添加 `errorClass`,但是只有在 `isActive` 是 truthy[[1]](#footnote-1) 时才添加 `activeClass`。'
152 | )
153 | })
154 | test('[md] space between "&" punctuation', () => {
155 | expect(getOutput('## 访问元素 & 组件')).toBe('## 访问元素 & 组件')
156 | })
157 | test('[md] duplicated space outside hyper content', () => {
158 | expect(
159 | getOutput(
160 | '那么你可以通过 [`$forceUpdate`](../api/#vm-forceUpdate) 来做这件事。'
161 | )
162 | ).toBe(
163 | '那么你可以通过 [`$forceUpdate`](../api/#vm-forceUpdate) 来做这件事。'
164 | )
165 | })
166 | test('[md] opposite side of hyper mark and bracket mark', () => {
167 | expect(
168 | getOutput(
169 | '注意 **`v-slot` 只能添加在 `` 上** (只有[一种例外情况](#独占默认插槽的缩写语法)),这一点和已经废弃的 [`slot` 特性](#废弃了的语法)不同。'
170 | )
171 | ).toBe(
172 | '注意 **`v-slot` 只能添加在 `` 上** (只有[一种例外情况](#独占默认插槽的缩写语法)),这一点和已经废弃的 [`slot` 特性](#废弃了的语法)不同。'
173 | )
174 | })
175 | test('[md] space before punctuation', () => {
176 | expect(getOutput('不过在需要时你也可以提供一个 setter :')).toBe(
177 | '不过在需要时你也可以提供一个 setter:'
178 | )
179 | })
180 | test('[md] periods as ellipsis', () => {
181 | expect(
182 | getOutput(
183 | '你可以使用 [`try`...`catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) 作为替代。'
184 | )
185 | ).toBe(
186 | '你可以使用 [`try`...`catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) 作为替代。'
187 | )
188 | })
189 | test('[md] space between punctuation and hyper content', () => {
190 | expect(
191 | getOutput(
192 | 'store 实例不再暴露事件触发器 (event emitter) 接口 (`on`, `off`, `emit`)。'
193 | )
194 | ).toBe(
195 | 'store 实例不再暴露事件触发器 (event emitter) 接口 (`on`,`off`,`emit`)。'
196 | )
197 | })
198 | test('[md] html entity', () => {
199 | expect(
200 | getOutput(
201 | '取决于你分心和开始 2.0 最酷的新功能的次数。😉 无法判断时间,'
202 | )
203 | ).toBe('取决于你分心和开始 2.0 最酷的新功能的次数。😉 无法判断时间,')
204 | })
205 | test('[md] space between dash', () => {
206 | expect(
207 | getOutput('可以阅读本页面剩余部分 - 或者从[介绍](index.html)部分')
208 | ).toBe('可以阅读本页面剩余部分 - 或者从[介绍](index.html)部分')
209 | })
210 | test('[md] space between slash', () => {
211 | expect(
212 | getOutput('为此还应该引入 `Vue.nextTick`/`vm.$nextTick`。例如:')
213 | ).toBe('为此还应该引入 `Vue.nextTick`/`vm.$nextTick`。例如:')
214 | })
215 | test('[md] space outside hyper mark and hyper content', () => {
216 | expect(
217 | getOutput(
218 | '这种写法的更多优点详见:[`v-model` 示例](#带有-debounce-的-v-model移除)。'
219 | )
220 | ).toBe(
221 | '这种写法的更多优点详见:[`v-model` 示例](#带有-debounce-的-v-model移除)。'
222 | )
223 | })
224 | test('[md] space between punctuation and hyper content', () => {
225 | expect(
226 | getOutput(
227 | '对于布尔特性 (它们只要存在就意味着值为 `true`),`v-bind` 工作起来略有不同'
228 | )
229 | ).toBe(
230 | '对于布尔特性 (它们只要存在就意味着值为 `true`),`v-bind` 工作起来略有不同'
231 | )
232 | })
233 | test('[md] star (not punctuation)', () => {
234 | expect(getOutput('切换到 *Archive* 标签,然后再切换回 *Posts*')).toBe(
235 | '切换到 *Archive* 标签,然后再切换回 *Posts*'
236 | )
237 | })
238 | test('[md] colon (not datetime)', () => {
239 | expect(
240 | getOutput(
241 | '1. 添加全局方法或者属性。如: [vue-custom-element](https://github.com/karol-f/vue-custom-element)'
242 | )
243 | ).toBe(
244 | '1. 添加全局方法或者属性。如:[vue-custom-element](https://github.com/karol-f/vue-custom-element)'
245 | )
246 | })
247 | test('[md] escaped markdown syntax', () => {
248 | expect(
249 | getOutput(
250 | '2. 开发者向 Vue 挂载包含服务端渲染或用户提供的内容的 HTML 的整个页面。这实质上和问题 \\#1 是相同的,但是有的时候开发者可能没有意识到。这会使得攻击者提供作为普通 HTML 安全但对于 Vue 模板不安全的 HTML 以导致安全漏洞。最佳实践是永远不要向 Vue 挂载可能包含服务端渲染或用户提供的内容。'
251 | )
252 | ).toBe(
253 | '2. 开发者向 Vue 挂载包含服务端渲染或用户提供的内容的 HTML 的整个页面。这实质上和问题 \\#1 是相同的,但是有的时候开发者可能没有意识到。这会使得攻击者提供作为普通 HTML 安全但对于 Vue 模板不安全的 HTML 以导致安全漏洞。最佳实践是永远不要向 Vue 挂载可能包含服务端渲染或用户提供的内容。'
254 | )
255 | })
256 | test('[md] bracket x html tag', () => {
257 | expect(
258 | getOutput(
259 | '引入一个工厂函数 (factory function)使得我们的测试更简洁更易读'
260 | )
261 | ).toBe(
262 | '引入一个工厂函数 (factory function) 使得我们的测试更简洁更易读'
263 | )
264 | })
265 | test('[md] special quotations group inside md mark', () => {
266 | expect(
267 | getOutput(
268 | '更多测试 Vue 组件的知识可翻阅核心团员 [Edd Yerburgh](https://eddyerburgh.me/) 的书[《测试 Vue.js 应用》](https://www.manning.com/books/testing-vuejs-applications)。'
269 | )
270 | ).toBe(
271 | '更多测试 Vue 组件的知识可翻阅核心团员 [Edd Yerburgh](https://eddyerburgh.me/) 的书[《测试 Vue.js 应用》](https://www.manning.com/books/testing-vuejs-applications)。'
272 | )
273 | })
274 | test('[md] blockquote', () => {
275 | expect(
276 | getOutput(
277 | 'foo\n\n> `components/icons/IconBox.vue`\n> `components/icons/IconCalendar.vue`\n> `components/icons/IconEnvelope.vue`\n\nbar'
278 | )
279 | ).toBe(
280 | 'foo\n\n> `components/icons/IconBox.vue`\n> `components/icons/IconCalendar.vue`\n> `components/icons/IconEnvelope.vue`\n\nbar'
281 | )
282 | })
283 | test('[md] spaces in blockquotes', () => {
284 | expect(
285 | getOutput(
286 | `> [Live Demo ](https://vue-hn.herokuapp.com/)\n> 注:如果在一段时间内没有人访问过该网站,则需要一些加载时间。\n>`
287 | )
288 | ).toBe(
289 | `> [Live Demo](https://vue-hn.herokuapp.com/)\n> 注:如果在一段时间内没有人访问过该网站,则需要一些加载时间。\n>`
290 | )
291 | })
292 | test('[md] infinite findMarkSeq bug', () => {
293 | expect(getOutput('注意**局部注册的组件在其子组件中*不可用***。')).toBe(
294 | '注意**局部注册的组件在其子组件中*不可用***。'
295 | )
296 | })
297 | test('[md] linebreak', () => {
298 | expect(
299 | getOutput(
300 | 'XXXX\n{% raw %}XXX{% endraw %}\n{% raw %}XXX{% endraw %}\n### XXXX'
301 | )
302 | ).toBe('XXXX\n{% raw %}XXX{% endraw %}\n{% raw %}XXX{% endraw %}\n### XXXX')
303 | })
304 | test('[md] space before link', () => {
305 | expect(
306 | getOutput('为了替换 `双向` 指令,见 [示例](#双向过滤器-替换)。')
307 | ).toBe('为了替换 `双向` 指令,见[示例](#双向过滤器-替换)。')
308 | expect(getOutput('详见 [自定义指令指南](custom-directive.html)。')).toBe(
309 | '详见[自定义指令指南](custom-directive.html)。'
310 | )
311 | })
312 | test('[md] space for md marker in the front', () => {
313 | expect(
314 | getOutput(
315 | '- [`` API 参考](/api/built-in-components.html#keepalive)'
316 | )
317 | ).toBe(
318 | '- [`` API 参考](/api/built-in-components.html#keepalive)'
319 | )
320 | })
321 | })
322 |
--------------------------------------------------------------------------------
/src/parser/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileOverview
3 | *
4 | * This file contains the types for the parser.
5 | *
6 | * - Chars
7 | * - Pairs
8 | * - Marks
9 | * - Tokens
10 | */
11 |
12 | import { Validation } from '../report.js'
13 |
14 | // Char
15 |
16 | export enum CharType {
17 | EMPTY = 'empty',
18 |
19 | SPACE = 'space',
20 |
21 | WESTERN_LETTER = 'western-letter',
22 | CJK_CHAR = 'cjk-char',
23 |
24 | // periods, commas, secondary commas, colons, semicolons, exclamation marks, question marks, etc.
25 | HALFWIDTH_PAUSE_OR_STOP = 'halfwidth-pause-or-stop',
26 | FULLWIDTH_PAUSE_OR_STOP = 'fullwidth-pause-or-stop',
27 |
28 | // single, double, corner, white corner
29 | // + book title marks
30 | // left x right
31 | HALFWIDTH_QUOTATION = 'halfwidth-quotation',
32 | FULLWIDTH_QUOTATION = 'fullwidth-quotation',
33 |
34 | // parentheses
35 | HALFWIDTH_BRACKET = 'halfwidth-bracket',
36 | FULLWIDTH_BRACKET = 'fullwidth-bracket',
37 |
38 | // // parenthesis, black lenticular brackets, white lenticular brackets,
39 | // // square brackets, tortoise shell brackets, curly brackets
40 | // // left x right
41 | // PARENTHESIS = 'parenthesis',
42 | // // double angle brackets, angle brackets
43 | // // left x right
44 | // BOOK_TITLE_MARK = 'book-title',
45 |
46 | // dashes, ellipsis, connector marks, interpuncts, proper noun marks, solidi, etc.
47 | HALFWIDTH_OTHER_PUNCTUATION = 'halfwidth-other-punctuation',
48 | FULLWIDTH_OTHER_PUNCTUATION = 'fullwidth-other-punctuation',
49 |
50 | // // ⁈, ⁇, ‼, ⁉
51 | // SPECIAL_PUNCTUATION_MARK = 'special-punctuation',
52 |
53 | UNKNOWN = 'unknown'
54 | }
55 |
56 | type CharSet = {
57 | [setName: string]: string
58 | }
59 |
60 | export const BRACKET_CHAR_SET: CharSet = {
61 | left: '([{(〔[{',
62 | right: ')]})〕]}'
63 | }
64 | export const QUOTATION_CHAR_SET: CharSet = {
65 | left: `“‘《〈『「【〖`,
66 | right: `”’》〉』」】〗`,
67 | neutral: `'"`
68 | }
69 | export const SHORTHAND_CHARS = `'’`
70 | export const SHORTHAND_PAIR_SET: CharSet = {
71 | [`'`]: `'`,
72 | [`’`]: `‘`
73 | }
74 |
75 | const FULLWIDTH_PAIRS = `“”‘’()〔〕[]{}《》〈〉「」『』【】〖〗`
76 |
77 | export const isFullwidthPair = (str: string): boolean =>
78 | FULLWIDTH_PAIRS.indexOf(str) >= 0
79 |
80 | // Reusable
81 |
82 | type Pair = {
83 | startIndex: number
84 | startValue: string
85 | endIndex: number
86 | endValue: string
87 | }
88 |
89 | type MutablePair = {
90 | modifiedStartValue: string
91 | ignoredStartValue?: string
92 | modifiedEndValue: string
93 | ignoredEndValue?: string
94 | }
95 |
96 | // Mark
97 |
98 | /**
99 | * Marks are hyper info, including content and wrappers.
100 | * They are categorized by parsers, not by usage.
101 | */
102 | export enum MarkType {
103 | /**
104 | * Brackets
105 | */
106 | BRACKETS = 'brackets',
107 | /**
108 | * Inline Markdown marks
109 | */
110 | HYPER = 'hyper',
111 | /**
112 | * - \`xxx\`
113 | * - <code>xxx</code>
114 | * - Hexo/VuePress container
115 | * - Other html code
116 | */
117 | RAW = 'raw'
118 | }
119 |
120 | export enum MarkSideType {
121 | LEFT = 'left',
122 | RIGHT = 'right'
123 | }
124 |
125 | export type Mark = Pair & {
126 | type: MarkType
127 | meta?: string // TODO: AST type enum
128 | }
129 |
130 | export type RawMark = Mark & {
131 | code: MarkSideType
132 | rightPair?: RawMark
133 | }
134 |
135 | export type MutableMark = Mark & MutablePair
136 |
137 | export type MutableRawMark = RawMark & MutablePair
138 |
139 | export type MarkMap = {
140 | [index: number]: Mark
141 | }
142 |
143 | export const isRawMark = (mark: Mark): mark is RawMark => {
144 | return (mark as RawMark).code !== undefined
145 | }
146 |
147 | // Token type
148 |
149 | export type LetterType = CharType.WESTERN_LETTER | CharType.CJK_CHAR
150 |
151 | export type PauseOrStopType =
152 | | CharType.HALFWIDTH_PAUSE_OR_STOP
153 | | CharType.FULLWIDTH_PAUSE_OR_STOP
154 |
155 | export type QuotationType =
156 | | CharType.HALFWIDTH_QUOTATION
157 | | CharType.FULLWIDTH_QUOTATION
158 |
159 | export type BracketType =
160 | | CharType.HALFWIDTH_BRACKET
161 | | CharType.FULLWIDTH_BRACKET
162 |
163 | export type OtherPunctuationType =
164 | | CharType.HALFWIDTH_OTHER_PUNCTUATION
165 | | CharType.FULLWIDTH_OTHER_PUNCTUATION
166 |
167 | export type SinglePunctuationType = PauseOrStopType | OtherPunctuationType
168 |
169 | export type PunctuationType = SinglePunctuationType | BracketType
170 |
171 | export type NormalContentTokenType = LetterType | SinglePunctuationType
172 |
173 | export type HalfwidthPuntuationType =
174 | | CharType.HALFWIDTH_PAUSE_OR_STOP
175 | | CharType.HALFWIDTH_QUOTATION
176 | | CharType.HALFWIDTH_BRACKET
177 | | CharType.HALFWIDTH_OTHER_PUNCTUATION
178 |
179 | export type FullwidthPuntuationType =
180 | | CharType.FULLWIDTH_PAUSE_OR_STOP
181 | | CharType.FULLWIDTH_QUOTATION
182 | | CharType.FULLWIDTH_BRACKET
183 | | CharType.FULLWIDTH_OTHER_PUNCTUATION
184 |
185 | export type HalfwidthTokenType =
186 | | CharType.WESTERN_LETTER
187 | | FullwidthPuntuationType
188 |
189 | export type FullwidthTokenType = CharType.CJK_CHAR | FullwidthPuntuationType
190 |
191 | /**
192 | * TODO: paired html tags should be hyper mark
193 | */
194 | export enum HyperTokenType {
195 | /**
196 | * Brackets
197 | */
198 | BRACKET_MARK = 'bracket-mark',
199 | /**
200 | * Inline Markdown marks
201 | */
202 | HYPER_MARK = 'hyper-mark',
203 |
204 | /**
205 | * - \`xxx\`
206 | * - <code>xxx</code>
207 | */
208 | CODE_CONTENT = 'code-content',
209 | /**
210 | * - Hexo/VuePress container
211 | * - Other html code
212 | */
213 | HYPER_CONTENT = 'hyper-content',
214 |
215 | /**
216 | * Unpaired brackets/quotations
217 | */
218 | UNMATCHED = 'unmatched',
219 | /**
220 | * For indeterminate tokens
221 | */
222 | INDETERMINATED = 'indeterminated'
223 | }
224 |
225 | export enum GroupTokenType {
226 | GROUP = 'group'
227 | }
228 |
229 | export type SingleTokenType = NormalContentTokenType | HyperTokenType
230 |
231 | export type TokenType = SingleTokenType | GroupTokenType
232 |
233 | export type NonTokenCharType =
234 | | BracketType
235 | | QuotationType
236 | | CharType.EMPTY
237 | | CharType.SPACE
238 | | CharType.UNKNOWN
239 |
240 | export type GeneralType = TokenType | NonTokenCharType
241 |
242 | export const getHalfwidthTokenType = (type: TokenType): TokenType => {
243 | switch (type) {
244 | case CharType.CJK_CHAR:
245 | return CharType.WESTERN_LETTER
246 | case CharType.FULLWIDTH_PAUSE_OR_STOP:
247 | return CharType.HALFWIDTH_PAUSE_OR_STOP
248 | case CharType.FULLWIDTH_OTHER_PUNCTUATION:
249 | return CharType.HALFWIDTH_OTHER_PUNCTUATION
250 | }
251 | return type
252 | }
253 |
254 | export const getFullwidthTokenType = (type: TokenType): TokenType => {
255 | switch (type) {
256 | case CharType.WESTERN_LETTER:
257 | return CharType.CJK_CHAR
258 | case CharType.HALFWIDTH_PAUSE_OR_STOP:
259 | return CharType.FULLWIDTH_PAUSE_OR_STOP
260 | case CharType.HALFWIDTH_OTHER_PUNCTUATION:
261 | return CharType.FULLWIDTH_OTHER_PUNCTUATION
262 | }
263 | return type
264 | }
265 |
266 | export type NonCodeVisibleTokenType =
267 | | NormalContentTokenType
268 | | HyperTokenType.BRACKET_MARK
269 | | GroupTokenType.GROUP
270 |
271 | export type VisibleTokenType =
272 | | NonCodeVisibleTokenType
273 | | HyperTokenType.CODE_CONTENT
274 |
275 | export type InvisibleTokenType = HyperTokenType.HYPER_MARK
276 |
277 | export type VisibilityUnknownTokenType = HyperTokenType.HYPER_CONTENT
278 |
279 | export const isLetterType = (type: GeneralType): type is LetterType => {
280 | return type === CharType.WESTERN_LETTER || type === CharType.CJK_CHAR
281 | }
282 |
283 | export const isPauseOrStopType = (
284 | type: GeneralType
285 | ): type is PauseOrStopType => {
286 | return (
287 | type === CharType.HALFWIDTH_PAUSE_OR_STOP ||
288 | type === CharType.FULLWIDTH_PAUSE_OR_STOP
289 | )
290 | }
291 |
292 | export const isQuotationType = (type: GeneralType): type is QuotationType => {
293 | return (
294 | type === CharType.HALFWIDTH_QUOTATION ||
295 | type === CharType.FULLWIDTH_QUOTATION
296 | )
297 | }
298 |
299 | export const isBracketType = (type: GeneralType): type is BracketType => {
300 | return (
301 | type === CharType.HALFWIDTH_BRACKET || type === CharType.FULLWIDTH_BRACKET
302 | )
303 | }
304 |
305 | export const isOtherPunctuationType = (
306 | type: GeneralType
307 | ): type is OtherPunctuationType => {
308 | return (
309 | type === CharType.HALFWIDTH_OTHER_PUNCTUATION ||
310 | type === CharType.FULLWIDTH_OTHER_PUNCTUATION
311 | )
312 | }
313 |
314 | export const isSinglePunctuationType = (
315 | type: GeneralType
316 | ): type is SinglePunctuationType => {
317 | return isPauseOrStopType(type) || isOtherPunctuationType(type)
318 | }
319 |
320 | export const isPunctuationType = (
321 | type: GeneralType
322 | ): type is PunctuationType => {
323 | return (
324 | isPauseOrStopType(type) ||
325 | isQuotationType(type) ||
326 | isBracketType(type) ||
327 | isOtherPunctuationType(type)
328 | )
329 | }
330 |
331 | export const isHalfwidthPunctuationType = (
332 | type: GeneralType
333 | ): type is HalfwidthPuntuationType => {
334 | return (
335 | type === CharType.HALFWIDTH_PAUSE_OR_STOP ||
336 | type === CharType.HALFWIDTH_QUOTATION ||
337 | type === CharType.HALFWIDTH_BRACKET ||
338 | type === CharType.HALFWIDTH_OTHER_PUNCTUATION
339 | )
340 | }
341 |
342 | export const isHalfwidthType = (
343 | type: GeneralType
344 | ): type is HalfwidthTokenType => {
345 | return type === CharType.WESTERN_LETTER || isHalfwidthPunctuationType(type)
346 | }
347 |
348 | export const isFullwidthPunctuationType = (
349 | type: GeneralType
350 | ): type is FullwidthPuntuationType => {
351 | return (
352 | type === CharType.FULLWIDTH_PAUSE_OR_STOP ||
353 | type === CharType.FULLWIDTH_QUOTATION ||
354 | type === CharType.FULLWIDTH_BRACKET ||
355 | type === CharType.FULLWIDTH_OTHER_PUNCTUATION
356 | )
357 | }
358 |
359 | export const isFullwidthType = (
360 | type: GeneralType
361 | ): type is FullwidthTokenType => {
362 | return type === CharType.CJK_CHAR || isFullwidthPunctuationType(type)
363 | }
364 |
365 | export const isNonCodeVisibleType = (type: GeneralType): type is LetterType => {
366 | return (
367 | isLetterType(type) ||
368 | isSinglePunctuationType(type) ||
369 | type === HyperTokenType.BRACKET_MARK ||
370 | type === GroupTokenType.GROUP
371 | )
372 | }
373 |
374 | export const isVisibleType = (type: GeneralType): type is VisibleTokenType => {
375 | return isNonCodeVisibleType(type) || type === HyperTokenType.CODE_CONTENT
376 | }
377 |
378 | export const isInvisibleType = (
379 | type: GeneralType
380 | ): type is InvisibleTokenType => {
381 | // OTHERS?
382 | return type === HyperTokenType.HYPER_MARK
383 | }
384 |
385 | export const isVisibilityUnknownType = (
386 | type: GeneralType
387 | ): type is VisibilityUnknownTokenType => {
388 | return type === HyperTokenType.HYPER_CONTENT
389 | }
390 |
391 | // Token
392 |
393 | type CommonToken = {
394 | index: number
395 | length: number
396 |
397 | value: string
398 | spaceAfter: string
399 |
400 | mark?: Mark
401 | markSide?: MarkSideType
402 | }
403 |
404 | type MutableCommonToken = CommonToken & {
405 | modifiedValue: string
406 | ignoredValue?: string
407 | modifiedSpaceAfter: string
408 | ignoredSpaceAfter?: string
409 | validations: Validation[]
410 | }
411 |
412 | export type SingleToken = CommonToken & {
413 | type: SingleTokenType
414 | }
415 |
416 | export type MutableSingleToken = MutableCommonToken & {
417 | type: SingleTokenType
418 | modifiedType: SingleTokenType
419 | ignoredType?: SingleTokenType
420 | }
421 |
422 | export type GroupToken = Array &
423 | CommonToken &
424 | Pair & {
425 | type: GroupTokenType
426 | innerSpaceBefore: string
427 | }
428 |
429 | export type MutableGroupToken = Array &
430 | MutableCommonToken &
431 | Pair &
432 | MutablePair & {
433 | type: GroupTokenType
434 | modifiedType: GroupTokenType
435 | ignoredType?: GroupTokenType
436 | innerSpaceBefore: string
437 | modifiedInnerSpaceBefore: string
438 | ignoredInnerSpaceBefore?: string
439 | }
440 |
441 | export type Token = SingleToken | GroupToken
442 |
443 | export type MutableToken = MutableSingleToken | MutableGroupToken
444 |
--------------------------------------------------------------------------------