Skip to content

Commit 7c05d81

Browse files
committed
VS Code: move DocumentLinter into lower-level module
I want to test DocumentLinter more directly. Discourage DocumentLinter depending on VS Code by moving it into the wasm/ directory. Replace uses of Node.js' assert module to make the code web-compatible. This commit should not change behavior.
1 parent 024f937 commit 7c05d81

File tree

3 files changed

+289
-281
lines changed

3 files changed

+289
-281
lines changed

plugin/vscode/extension.js

Lines changed: 1 addition & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -3,294 +3,16 @@
33

44
"use strict";
55

6-
let assert = require("assert");
76
let vscode = require("vscode");
87
let {
98
DiagnosticSeverity,
10-
ProcessCrashed,
9+
DocumentLinter,
1110
createProcessFactoryAsync,
1211
} = require("quick-lint-js-wasm/quick-lint-js.js");
1312

1413
// TODO(strager): Allow developers to reload the .wasm file.
1514
let processFactoryPromise = createProcessFactoryAsync();
1615

17-
let DocumentLinterState = {
18-
// A DocumentForVSCode hasn't been created yet.
19-
NO_PARSER: "NO_PARSER",
20-
21-
// A DocumentForVSCode is in the process of being created.
22-
CREATING_PARSER: "CREATING_PARSER",
23-
24-
// A DocumentForVSCode has been created, but it has no text.
25-
PARSER_UNINITIALIZED: "PARSER_UNINITIALIZED",
26-
27-
// A DocumentForVSCode has been created, and its text is up-to-date with the
28-
// vscode.Document.
29-
PARSER_LOADED: "PARSER_LOADED",
30-
31-
// The DocumentForVSCode's Process crashed, and we are creating a new Process
32-
// and DocumentForVSCode.
33-
//
34-
// Document changes should be queued.
35-
RECOVERING: "RECOVERING",
36-
37-
DISPOSED: "DISPOSED",
38-
};
39-
40-
class LintingCrashed extends Error {
41-
constructor(originalError) {
42-
super(String(originalError));
43-
this.originalError = originalError;
44-
}
45-
}
46-
exports.LintingCrashed = LintingCrashed;
47-
48-
class DocumentLinterDisposed extends Error {}
49-
exports.DocumentLinterDisposed = DocumentLinterDisposed;
50-
51-
class DocumentLinter {
52-
// document has the following methods:
53-
//
54-
// getText(): string;
55-
// setDiagnostics(diagnostics: Object[]): void;
56-
// removeDiagnostics(): void;
57-
constructor(document, processFactoryPromise) {
58-
this._document = document;
59-
this._processFactoryPromise = processFactoryPromise;
60-
this._state = DocumentLinterState.NO_PARSER;
61-
62-
// Used only in states: CREATING_PARSER
63-
this._parserPromise = null;
64-
65-
// Used only in states: PARSER_UNINITIALIZED, PARSER_LOADED
66-
this._parser = null;
67-
68-
// Used only in states: PARSER_LOADED, RECOVERING
69-
this._pendingChanges = [];
70-
71-
// Used only in states: RECOVERING
72-
this._recoveryPromise = null;
73-
}
74-
75-
async _createParser() {
76-
assert.strictEqual(this._state, DocumentLinterState.NO_PARSER);
77-
this._state = DocumentLinterState.CREATING_PARSER;
78-
this._parserPromise = (async () => {
79-
let factory = await this._processFactoryPromise;
80-
// TODO(strager): Reuse processes across documents.
81-
let process = await factory.createProcessAsync();
82-
let parser = await process.createDocumentForVSCodeAsync();
83-
84-
if (this._state === DocumentLinterState.DISPOSED) {
85-
parser.dispose();
86-
throw new DocumentLinterDisposed();
87-
}
88-
assert.strictEqual(this._state, DocumentLinterState.CREATING_PARSER);
89-
this._parser = parser;
90-
this._state = DocumentLinterState.PARSER_UNINITIALIZED;
91-
return parser;
92-
})();
93-
return await this._parserPromise;
94-
}
95-
96-
async disposeAsync() {
97-
let oldState = this._state;
98-
this._state = DocumentLinterState.DISPOSED;
99-
switch (oldState) {
100-
case DocumentLinterState.NO_PARSER:
101-
break;
102-
103-
case DocumentLinterState.CREATING_PARSER:
104-
case DocumentLinterState.PARSER_UNINITIALIZED:
105-
case DocumentLinterState.PARSER_LOADED: {
106-
try {
107-
await this._parserPromise;
108-
} catch (e) {
109-
if (!(e instanceof DocumentLinterDisposed)) {
110-
throw e;
111-
}
112-
}
113-
if (this._parser !== null) {
114-
this._parser.dispose();
115-
}
116-
break;
117-
}
118-
119-
case DocumentLinterState.DISPOSED:
120-
// TODO(strager): Should double-dispose be okay?
121-
throw new DocumentLinterDisposed();
122-
123-
default:
124-
throw new Error(`Unexpected linter state: ${this._state}`);
125-
}
126-
this._document.removeDiagnostics();
127-
}
128-
129-
dispose() {
130-
logAsyncErrors(async () => {
131-
await this.disposeAsync();
132-
});
133-
}
134-
135-
async editorChangedVisibilityAsync() {
136-
switch (this._state) {
137-
case DocumentLinterState.NO_PARSER:
138-
await this._createParser();
139-
await this.editorChangedVisibilityAsync();
140-
break;
141-
142-
case DocumentLinterState.CREATING_PARSER:
143-
await this._parserPromise;
144-
await this.editorChangedVisibilityAsync();
145-
break;
146-
147-
case DocumentLinterState.PARSER_UNINITIALIZED:
148-
await this._initializeParserAsync();
149-
break;
150-
151-
case DocumentLinterState.PARSER_LOADED:
152-
// No changes could have been made with the editor closed. Ignore.
153-
break;
154-
155-
case DocumentLinterState.DISPOSED:
156-
throw new DocumentLinterDisposed();
157-
158-
default:
159-
throw new Error(`Unexpected linter state: ${this._state}`);
160-
}
161-
}
162-
163-
async textChangedAsync(changes) {
164-
// BEGIN CRITICAL SECTION (no awaiting below)
165-
switch (this._state) {
166-
case DocumentLinterState.NO_PARSER:
167-
// END CRITICAL SECTION (no awaiting above)
168-
await this._createParser();
169-
await this._initializeParserAsync();
170-
break;
171-
172-
case DocumentLinterState.CREATING_PARSER:
173-
// END CRITICAL SECTION (no awaiting above)
174-
await this._parserPromise;
175-
await this._initializeParserAsync();
176-
break;
177-
178-
case DocumentLinterState.PARSER_UNINITIALIZED:
179-
// END CRITICAL SECTION (no awaiting above)
180-
await this._initializeParserAsync();
181-
break;
182-
183-
case DocumentLinterState.PARSER_LOADED:
184-
this._pendingChanges.push(...changes);
185-
try {
186-
for (let change of this._pendingChanges) {
187-
this._parser.replaceText(change.range, change.text);
188-
}
189-
this._pendingChanges.length = 0;
190-
// END CRITICAL SECTION (no awaiting above)
191-
192-
let diags = this._parser.lint();
193-
this._document.setDiagnostics(diags);
194-
} catch (e) {
195-
// END CRITICAL SECTION (no awaiting above)
196-
if (e instanceof ProcessCrashed) {
197-
await this._recoverFromCrashAsync(e);
198-
} else {
199-
throw e;
200-
}
201-
}
202-
break;
203-
204-
case DocumentLinterState.RECOVERING:
205-
this._pendingChanges.push(...changes);
206-
// END CRITICAL SECTION (no awaiting above)
207-
await this._recoveryPromise;
208-
// Changes should have been applied during recovery.
209-
assert.strictEqual(this._pendingChanges.includes(changes[0]), false);
210-
break;
211-
212-
case DocumentLinterState.DISPOSED:
213-
throw new DocumentLinterDisposed();
214-
215-
default:
216-
throw new Error(`Unexpected linter state: ${this._state}`);
217-
}
218-
}
219-
220-
// Transition: PARSER_UNINITIALIZED -> PARSER_LOADED (or NO_PARSER on error)
221-
async _initializeParserAsync() {
222-
// BEGIN CRITICAL SECTION (no awaiting below)
223-
assert.strictEqual(this._state, DocumentLinterState.PARSER_UNINITIALIZED);
224-
try {
225-
this._parser.replaceText(
226-
{
227-
start: { line: 0, character: 0 },
228-
end: { line: 0, character: 0 },
229-
},
230-
this._document.getText()
231-
);
232-
this._pendingChanges.length = 0;
233-
this._state = DocumentLinterState.PARSER_LOADED;
234-
// END CRITICAL SECTION (no awaiting above)
235-
236-
let diags = this._parser.lint();
237-
this._document.setDiagnostics(diags);
238-
} catch (e) {
239-
if (e instanceof ProcessCrashed) {
240-
await this._recoverFromCrashAsync(e);
241-
} else {
242-
throw e;
243-
}
244-
}
245-
}
246-
247-
// Transition: any -> RECOVERING -> PARSER_LOADED (or NO_PARSER on error)
248-
async _recoverFromCrashAsync(error) {
249-
// BEGIN CRITICAL SECTION (no awaiting below)
250-
console.warn(
251-
`[quick-lint-js] warning: Parser process crashed. Recovering: ${error.stack}`
252-
);
253-
this._state = DocumentLinterState.RECOVERING;
254-
this._recoveryPromise = (async () => {
255-
let diags;
256-
try {
257-
// TODO(strager): Reuse processes across documents.
258-
let factory = await this._processFactoryPromise;
259-
let process = await factory.createProcessAsync();
260-
let parser = await process.createDocumentForVSCodeAsync();
261-
262-
// BEGIN CRITICAL SECTION (no awaiting below)
263-
assert.strictEqual(this._state, DocumentLinterState.RECOVERING);
264-
parser.replaceText(
265-
{
266-
start: { line: 0, character: 0 },
267-
end: { line: 0, character: 0 },
268-
},
269-
this._document.getText()
270-
);
271-
this._pendingChanges.length = 0;
272-
this._parser = parser;
273-
this._state = DocumentLinterState.PARSER_LOADED;
274-
// END CRITICAL SECTION (no awaiting above)
275-
276-
diags = parser.lint();
277-
} catch (e) {
278-
this._parser = null;
279-
this._parserPromise = null;
280-
this._state = DocumentLinterState.NO_PARSER;
281-
if (e instanceof ProcessCrashed) {
282-
throw new LintingCrashed(e);
283-
} else {
284-
throw e;
285-
}
286-
}
287-
this._document.setDiagnostics(diags);
288-
})();
289-
// END CRITICAL SECTION (no awaiting above)
290-
await this._recoveryPromise;
291-
}
292-
}
293-
29416
class VSCodeDocumentLinter {
29517
constructor(document, diagnosticCollection) {
29618
this._documentLinter = new DocumentLinter(

plugin/vscode/test/other-tests.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ let tests = {
196196

197197
// Should not throw.
198198
await linter.disposeAsync();
199-
await assert.rejects(promise, extension.DocumentLinterDisposed);
199+
await assert.rejects(promise, qljs.DocumentLinterDisposed);
200200
},
201201

202202
"concurrent edits are applied in order of calls": async ({ addCleanup }) => {
@@ -344,7 +344,7 @@ let tests = {
344344
await callback();
345345
return false;
346346
} catch (e) {
347-
if (e instanceof extension.LintingCrashed) {
347+
if (e instanceof qljs.LintingCrashed) {
348348
return true;
349349
} else {
350350
throw e;

0 commit comments

Comments
 (0)