Skip to content

Commit 3b7c681

Browse files
committed
Chore: refactoring
1 parent 6d04136 commit 3b7c681

File tree

12 files changed

+242
-203
lines changed

12 files changed

+242
-203
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"browser": false
1616
},
1717
"globals": {
18+
"console": false,
1819
"document": false,
1920
"location": false,
2021
"window": false

src/app-state.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import pako from "pako"
2+
import defaultCode from "./app-state/default-code.js"
3+
import defaultConfig from "./app-state/default-config.js"
4+
import { linter, ruleCategories } from "./app-state/eslint.js"
5+
6+
/**
7+
* Convert an Unicode string to base64.
8+
* @param {string} text The string to convert.
9+
* @returns {string} Base64 string.
10+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa#Unicode_strings
11+
*/
12+
function encodeToBase64(text) {
13+
return window.btoa(unescape(encodeURIComponent(text)))
14+
}
15+
16+
/**
17+
* Convert a base64 string to Unicode.
18+
* @param {string} base64 The string to convert.
19+
* @returns {string} Unicode string.
20+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa#Unicode_strings
21+
*/
22+
function decodeFromBase64(base64) {
23+
return decodeURIComponent(escape(window.atob(base64)))
24+
}
25+
26+
/**
27+
* The state object for this application.
28+
*/
29+
export default class PlaygroundState {
30+
/**
31+
* Initialize this state.
32+
* @param {string} [serializedString] A serialized string to restore the state.
33+
*/
34+
constructor(serializedString = "") {
35+
const code = defaultCode
36+
const config = Object.assign({}, defaultConfig)
37+
config.rules = Object.assign({}, defaultConfig.rules)
38+
39+
this.code = code
40+
this.config = config
41+
this.indentSize = 2
42+
this.indentType = "space"
43+
this.editorType = "codeAndFixedCode"
44+
this.messages = []
45+
this.fixedCode = code
46+
this.fixedMessages = []
47+
48+
const deserialized =
49+
typeof serializedString === "string" &&
50+
this.deserialize(serializedString)
51+
52+
// Ensure the correct messages, fixedCode, and fixedMessages.
53+
if (!deserialized) {
54+
this.lint()
55+
}
56+
}
57+
58+
/**
59+
* Enabled rules.
60+
* @returns {object} The map-like object. Keys are the ID of enabled rules. Every value is 2.
61+
*/
62+
get _enabledRules() {
63+
const allRules = this.config.rules
64+
return Object.keys(allRules).reduce(
65+
(map, id) => {
66+
if (allRules[id] === 2) {
67+
map[id] = 2
68+
}
69+
return map
70+
},
71+
{}
72+
)
73+
}
74+
75+
/**
76+
* Actual config object to lint.
77+
* @type {object}
78+
*/
79+
get _configToLint() {
80+
// Adjust the indentation options to the editor settings.
81+
const config = Object.assign({}, this.config)
82+
const rules = config.rules = Object.assign({}, this.config.rules)
83+
const indentType = (this.indentType === "space") ? this.indentSize : "tab"
84+
rules.indent = [rules.indent, indentType]
85+
rules["vue/html-indent"] = [rules["vue/html-indent"], indentType]
86+
87+
return config
88+
}
89+
90+
/**
91+
* Serialize this state as a base64 string.
92+
* @returns {string} The serialized string.
93+
*/
94+
serialize() {
95+
const jsonString = JSON.stringify({
96+
code: this.code,
97+
rules: this._enabledRules,
98+
indentSize: this.indentSize,
99+
indentType: this.indentType,
100+
editorType: this.editorType,
101+
})
102+
const compressedString = pako.deflate(jsonString, { to: "string" })
103+
104+
return encodeToBase64(compressedString)
105+
}
106+
107+
/**
108+
* Deserialize a given serialized string then update this object.
109+
* @param {string} serializedString A serialized string.
110+
* @returns {boolean} `true` if this state object was changed.
111+
*/
112+
deserialize(serializedString) { //eslint-disable-line complexity
113+
if (serializedString === "") {
114+
return false
115+
}
116+
try {
117+
// For backward compatibility, it can address non-compressed data.
118+
const compressed = !serializedString.startsWith("eyJj")
119+
const decodedText = decodeFromBase64(serializedString)
120+
const jsonText = compressed ? pako.inflate(decodedText, { to: "string" }) : decodedText
121+
const json = JSON.parse(jsonText)
122+
let changed = false
123+
124+
if (typeof json === "object" && json !== null) {
125+
if (typeof json.code === "string" && json.code !== this.code) {
126+
this.code = json.code
127+
changed = true
128+
}
129+
if (typeof json.rules === "object" && json.rules !== null) {
130+
for (const id of Object.keys(this.config.rules)) {
131+
const value = json.rules[id] ? 2 : 0
132+
if (value !== this.config.rules[id]) {
133+
this.config.rules[id] = value
134+
changed = true
135+
}
136+
}
137+
}
138+
}
139+
if ((json.indentSize === 2 || json.indentSize === 4 || json.indentSize === 8) && json.indentSize !== this.indentSize) {
140+
this.indentSize = json.indentSize
141+
changed = true
142+
}
143+
if ((json.indentType === "space" || json.indentType === "tab") && json.indentType !== this.indentType) {
144+
this.indentType = json.indentType
145+
changed = true
146+
}
147+
if ((json.editorType === "codeAndFixedCode" || json.editorType === "codeOnly") && json.editorType !== this.editorType) {
148+
this.editorType = json.editorType
149+
changed = true
150+
}
151+
152+
// Update messages, fixedCode, and fixedMessages.
153+
if (changed) {
154+
this.lint()
155+
}
156+
157+
return changed
158+
}
159+
catch (error) {
160+
console.error(error) //eslint-disable-line no-console
161+
return false
162+
}
163+
}
164+
165+
/**
166+
* Execute ESLint to update messages, fixedCode, and fixedMessages.
167+
* @returns {void}
168+
*/
169+
lint() {
170+
const config = this._configToLint
171+
172+
// Fix
173+
try {
174+
// At first, fix because `linter.verifyAndFix` does not accept SourceCode object.
175+
const ret = linter.verifyAndFix(this.code, config, "vue-eslint-demo.vue")
176+
this.fixedCode = ret.output
177+
this.fixedMessages = ret.messages
178+
}
179+
catch (err) {
180+
this.fixedCode = this.code
181+
this.fixedMessages = [{
182+
fatal: true,
183+
severity: 2,
184+
message: err.message,
185+
line: 1,
186+
column: 0,
187+
}]
188+
}
189+
190+
// Lint
191+
try {
192+
this.messages = linter.verify(
193+
// Cannot reuse until https://github.com/eslint/eslint/pull/8755 is merged.
194+
// linter.getSourceCode(), // Reuse the AST of the previous `linter.verifyAndFix`.
195+
this.code,
196+
config,
197+
"vue-eslint-demo.vue"
198+
)
199+
}
200+
catch (err) {
201+
this.messages = [{
202+
fatal: true,
203+
severity: 2,
204+
message: err.message,
205+
line: 1,
206+
column: 0,
207+
}]
208+
}
209+
}
210+
}
211+
212+
export { ruleCategories }
File renamed without changes.
File renamed without changes.

src/eslint.js renamed to src/app-state/eslint.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,3 @@ const ruleCategories = (() => {
5858
})()
5959

6060
export { ruleCategories, linter }
61-
62-
/**
63-
* Get the document URL of a rule.
64-
* @param {string} ruleId The rule ID to get.
65-
* @returns {string|null} The document URL of the rule.
66-
*/
67-
export function getRuleUrl(ruleId) {
68-
if (ruleId == null) {
69-
return null
70-
}
71-
return ruleId.startsWith("vue/")
72-
? `https://github.com/vuejs/eslint-plugin-vue/tree/master/docs/rules/${ruleId.replace("vue/", "")}.md`
73-
: `https://eslint.org/docs/rules/${ruleId}`
74-
}

src/app.vue

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@
4242
@edit="onCodeChange"
4343
@initialize.once="onEditorInitialize"
4444
/>
45-
<message-list class="app__errors" :messages="messages" />
45+
<message-list
46+
class="app__errors"
47+
:messages="messages"
48+
/>
4649
</div>
4750
</div>
4851
<div class="app__footer">
@@ -60,11 +63,10 @@
6063
</template>
6164

6265
<script>
66+
import AppState from "./app-state.js"
6367
import CodeEditor from "./code-editor.vue"
6468
import MessageList from "./message-list.vue"
6569
import RuleSelect from "./rule-select.vue"
66-
import { linter } from "./eslint.js"
67-
import { deserializeState, serializeState } from "./state.js"
6870
import versions from "./versions.js"
6971
7072
export default {
@@ -77,7 +79,7 @@ export default {
7779
},
7880
7981
data() {
80-
return deserializeState(window.location.hash.slice(1))
82+
return new AppState(window.location.hash.slice(1))
8183
},
8284
8385
computed: {
@@ -97,18 +99,17 @@ export default {
9799
},
98100
},
99101
100-
// Use `watch` to re-use the internal state of the linter while making `messages` and `fixedCode`.
101102
watch: {
102103
code() {
103-
this.lint()
104+
this.$data.lint()
104105
this.applyUrlHash()
105106
},
106107
indentSize() {
107-
this.lint()
108+
this.$data.lint()
108109
this.applyUrlHash()
109110
},
110111
indentType() {
111-
this.lint()
112+
this.$data.lint()
112113
this.applyUrlHash()
113114
},
114115
editorType() {
@@ -117,7 +118,6 @@ export default {
117118
},
118119
119120
mounted() {
120-
this.lint()
121121
window.addEventListener("hashchange", this.onUrlHashChange)
122122
},
123123
beforeDestroey() {
@@ -135,67 +135,16 @@ export default {
135135
136136
onConfigChange() {
137137
// The inside of `this.config` was changed directly.
138-
this.lint()
138+
this.$data.lint()
139139
this.applyUrlHash()
140140
},
141141
142142
onUrlHashChange() {
143-
const newSerializedState = window.location.hash.slice(1)
144-
const oldSerializedState = serializeState(this.$data)
145-
if (newSerializedState !== oldSerializedState) {
146-
Object.assign(this.$data, deserializeState(newSerializedState))
147-
}
143+
this.$data.deserialize(window.location.hash.slice(1))
148144
},
149145
150146
applyUrlHash() {
151-
window.location.replace(`#${serializeState(this.$data)}`)
152-
},
153-
154-
lint() {
155-
// Adjust the indentation options to the editor settings.
156-
const config = Object.assign({}, this.config)
157-
const rules = config.rules = Object.assign({}, this.config.rules)
158-
const indentType = (this.indentType === "space") ? this.indentSize : "tab"
159-
rules.indent = [rules.indent, indentType]
160-
rules["vue/html-indent"] = [rules["vue/html-indent"], indentType]
161-
162-
// Fix
163-
try {
164-
// At first, fix because `linter.verifyAndFix` does not accept SourceCode object.
165-
const ret = linter.verifyAndFix(this.code, config, "vue-eslint-demo.vue")
166-
this.fixedCode = ret.output
167-
this.fixedMessages = ret.messages
168-
}
169-
catch (err) {
170-
this.fixedCode = this.code
171-
this.fixedMessages = [{
172-
fatal: true,
173-
severity: 2,
174-
message: err.message,
175-
line: 1,
176-
column: 0,
177-
}]
178-
}
179-
180-
// Lint
181-
try {
182-
this.messages = linter.verify(
183-
// Cannot reuse until https://github.com/eslint/eslint/pull/8755 is merged.
184-
// linter.getSourceCode(), // Reuse the AST of the previous `linter.verifyAndFix`.
185-
this.code,
186-
config,
187-
"vue-eslint-demo.vue"
188-
)
189-
}
190-
catch (err) {
191-
this.messages = [{
192-
fatal: true,
193-
severity: 2,
194-
message: err.message,
195-
line: 1,
196-
column: 0,
197-
}]
198-
}
147+
window.location.replace(`#${this.$data.serialize()}`)
199148
},
200149
},
201150
}

src/message-list.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
<script>
2323
import MdIcon from "./md-icon.vue"
24-
import { getRuleUrl } from "./eslint.js"
24+
import { getRuleUrl } from "./util.js"
2525
2626
export default {
2727
name: "MessageList",

src/rule-select-category.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353

5454
<script>
5555
import MdIcon from "./md-icon.vue"
56-
import { getRuleUrl } from "./eslint.js"
56+
import { getRuleUrl } from "./util.js"
5757
5858
export default {
5959
name: "RuleSelectCategory",

0 commit comments

Comments
 (0)