Skip to content

Commit c0a21b4

Browse files
authored
(feat) svelte-check: watch mode, ignore/fail-on-warnings options (#373)
#354 , #287
1 parent e84e0e2 commit c0a21b4

File tree

10 files changed

+218
-84
lines changed

10 files changed

+218
-84
lines changed

packages/language-server/src/lib/documents/DocumentManager.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ export class DocumentManager {
1818
public locked = new Set<string>();
1919
public deleteCandidates = new Set<string>();
2020

21-
constructor(private createDocument: (textDocument: TextDocumentItem) => Document) {}
21+
constructor(
22+
private createDocument: (textDocument: Pick<TextDocumentItem, 'text' | 'uri'>) => Document,
23+
) {}
2224

23-
openDocument(textDocument: TextDocumentItem): Document {
25+
openDocument(textDocument: Pick<TextDocumentItem, 'text' | 'uri'>): Document {
2426
let document: Document;
2527
if (this.documents.has(textDocument.uri)) {
2628
document = this.documents.get(textDocument.uri)!;
@@ -45,8 +47,9 @@ export class DocumentManager {
4547
}
4648

4749
getAllOpenedByClient() {
48-
return Array.from(this.documents.entries())
49-
.filter((doc) => this.openedInClient.has(doc[0]));
50+
return Array.from(this.documents.entries()).filter((doc) =>
51+
this.openedInClient.has(doc[0]),
52+
);
5053
}
5154

5255
releaseDocument(uri: string): void {
@@ -58,7 +61,6 @@ export class DocumentManager {
5861
}
5962
}
6063

61-
6264
closeDocument(uri: string) {
6365
const document = this.documents.get(uri);
6466
if (!document) {

packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,8 @@ export class LSAndTSDocResolver {
3434
private createDocument = (fileName: string, content: string) => {
3535
const uri = pathToUrl(fileName);
3636
const document = this.docManager.openDocument({
37-
languageId: 'svelte',
3837
text: content,
3938
uri,
40-
version: 0,
4139
});
4240
this.docManager.lockDocument(uri);
4341
return document;

packages/language-server/src/svelte-check.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { LSConfigManager } from './ls-config';
33
import { CSSPlugin, HTMLPlugin, PluginHost, SveltePlugin, TypeScriptPlugin } from './plugins';
44
import { Diagnostic } from 'vscode-languageserver';
55
import { Logger } from './logger';
6+
import { urlToPath } from './utils';
67

78
/**
89
* Small wrapper around PluginHost's Diagnostic Capabilities
@@ -30,17 +31,44 @@ export class SvelteCheck {
3031
}
3132

3233
/**
33-
* Gets diagnostics for a svelte file.
34+
* Creates/updates given document
3435
*
35-
* @param params Text and Uri of a svelte file
36+
* @param doc Text and Uri of the document
3637
*/
37-
async getDiagnostics(params: { text: string; uri: string }): Promise<Diagnostic[]> {
38+
upsertDocument(doc: { text: string; uri: string }) {
3839
this.docManager.openDocument({
39-
languageId: 'svelte',
40-
text: params.text,
41-
uri: params.uri,
42-
version: 1,
40+
text: doc.text,
41+
uri: doc.uri,
4342
});
44-
return await this.pluginHost.getDiagnostics({ uri: params.uri });
43+
this.docManager.markAsOpenedInClient(doc.uri);
44+
}
45+
46+
/**
47+
* Removes/closes document
48+
*
49+
* @param uri Uri of the document
50+
*/
51+
removeDocument(uri: string) {
52+
this.docManager.closeDocument(uri);
53+
this.docManager.releaseDocument(uri);
54+
}
55+
56+
/**
57+
* Gets the diagnostics for all currently open files.
58+
*/
59+
async getDiagnostics(): Promise<
60+
{ filePath: string; text: string; diagnostics: Diagnostic[] }[]
61+
> {
62+
return await Promise.all(
63+
this.docManager.getAllOpenedByClient().map(async (doc) => {
64+
const uri = doc[1].uri;
65+
const diagnostics = await this.pluginHost.getDiagnostics({ uri });
66+
return {
67+
filePath: urlToPath(uri) || '',
68+
text: this.docManager.documents.get(uri)?.getText() || '',
69+
diagnostics,
70+
};
71+
}),
72+
);
4573
}
4674
}

packages/language-server/test/lib/documents/DocumentManager.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('Document Manager', () => {
1111
text: 'Hello, world!',
1212
};
1313

14-
const createTextDocument = (textDocument: TextDocumentItem) =>
14+
const createTextDocument = (textDocument: Pick<TextDocumentItem, 'uri' | 'text'>) =>
1515
new Document(textDocument.uri, textDocument.text);
1616

1717
it('opens documents', () => {

packages/language-server/test/plugins/PluginHost.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ describe('PluginHost', () => {
1313
};
1414

1515
function setup<T>(pluginProviderStubs: T) {
16-
const createTextDocument = (textDocument: TextDocumentItem) =>
17-
new Document(textDocument.uri, textDocument.text);
18-
19-
const docManager = new DocumentManager(createTextDocument);
16+
const docManager = new DocumentManager(
17+
(textDocument) => new Document(textDocument.uri, textDocument.text),
18+
);
2019

2120
const pluginHost = new PluginHost(docManager, <any>{});
2221
const plugin = {

packages/svelte-check/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ Usage:
5454

5555
`--output <human|human-verbose|machine>`
5656

57+
`--watch` Will not exit after one pass but keep watching files for changes and rerun diagnostics.
58+
59+
`--ignore <files/folders to ignore, relative to workspace root, comma-separated. Example: --ignore dist,build>`
60+
61+
`--fail-on-warnings` Will also exit with error code when there are warnings
62+
5763
### More docs, preprocessor setup and troubleshooting
5864

5965
[See here](/docs/README.md).

packages/svelte-check/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"homepage": "https://github.com/sveltejs/language-tools#readme",
2121
"dependencies": {
2222
"chalk": "^4.0.0",
23+
"chokidar": "^3.4.1",
2324
"glob": "^7.1.6",
2425
"minimist": "^1.2.5",
2526
"svelte-language-server": "*",

packages/svelte-check/src/index.ts

Lines changed: 98 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SvelteCheck } from 'svelte-language-server';
1010
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol';
1111
import { URI } from 'vscode-uri';
1212
import { HumanFriendlyWriter, MachineFriendlyWriter, Writer } from './writers';
13+
import { watch } from 'chokidar';
1314

1415
const outputFormats = ['human', 'human-verbose', 'machine'] as const;
1516
type OutputFormat = typeof outputFormats[number];
@@ -20,58 +21,105 @@ type Result = {
2021
warningCount: number;
2122
};
2223

23-
async function getDiagnostics(workspaceUri: URI, writer: Writer): Promise<Result | null> {
24-
writer.start(workspaceUri.fsPath);
25-
26-
const svelteCheck = new SvelteCheck(workspaceUri.fsPath);
27-
24+
function openAllDocuments(
25+
workspaceUri: URI,
26+
filePathsToIgnore: string[],
27+
svelteCheck: SvelteCheck,
28+
) {
2829
const files = glob.sync('**/*.svelte', {
2930
cwd: workspaceUri.fsPath,
30-
ignore: ['node_modules/**'],
31+
ignore: ['node_modules/**'].concat(filePathsToIgnore.map((ignore) => `${ignore}/**`)),
3132
});
3233

3334
const absFilePaths = files.map((f) => path.resolve(workspaceUri.fsPath, f));
3435

35-
const result = {
36-
fileCount: absFilePaths.length,
37-
errorCount: 0,
38-
warningCount: 0,
39-
};
40-
4136
for (const absFilePath of absFilePaths) {
4237
const text = fs.readFileSync(absFilePath, 'utf-8');
38+
svelteCheck.upsertDocument({
39+
uri: URI.file(absFilePath).toString(),
40+
text,
41+
});
42+
}
43+
}
4344

44-
let res: Diagnostic[] = [];
45+
async function getDiagnostics(
46+
workspaceUri: URI,
47+
writer: Writer,
48+
svelteCheck: SvelteCheck,
49+
): Promise<Result | null> {
50+
writer.start(workspaceUri.fsPath);
4551

46-
try {
47-
res = await svelteCheck.getDiagnostics({
48-
uri: URI.file(absFilePath).toString(),
49-
text,
52+
try {
53+
const diagnostics = await svelteCheck.getDiagnostics();
54+
55+
const result: Result = {
56+
fileCount: diagnostics.length,
57+
errorCount: 0,
58+
warningCount: 0,
59+
};
60+
61+
for (const diagnostic of diagnostics) {
62+
writer.file(
63+
diagnostic.diagnostics,
64+
workspaceUri.fsPath,
65+
path.relative(workspaceUri.fsPath, diagnostic.filePath),
66+
diagnostic.text,
67+
);
68+
69+
diagnostic.diagnostics.forEach((d: Diagnostic) => {
70+
if (d.severity === DiagnosticSeverity.Error) {
71+
result.errorCount += 1;
72+
} else if (d.severity === DiagnosticSeverity.Warning) {
73+
result.warningCount += 1;
74+
}
5075
});
51-
} catch (err) {
52-
writer.failure(err);
53-
return null;
5476
}
5577

56-
writer.file(
57-
res,
58-
workspaceUri.fsPath,
59-
path.relative(workspaceUri.fsPath, absFilePath),
60-
text,
61-
);
78+
writer.completion(result.fileCount, result.errorCount, result.warningCount);
79+
return result;
80+
} catch (err) {
81+
writer.failure(err);
82+
return null;
83+
}
84+
}
6285

63-
res.forEach((d: Diagnostic) => {
64-
if (d.severity === DiagnosticSeverity.Error) {
65-
result.errorCount += 1;
66-
} else if (d.severity === DiagnosticSeverity.Warning) {
67-
result.warningCount += 1;
68-
}
69-
});
86+
class DiagnosticsWatcher {
87+
private updateDiagnostics: any;
88+
89+
constructor(
90+
private workspaceUri: URI,
91+
private svelteCheck: SvelteCheck,
92+
private writer: Writer,
93+
filePathsToIgnore: string[],
94+
) {
95+
watch(`${workspaceUri.fsPath}/**/*.svelte`, {
96+
ignored: ['node_modules']
97+
.concat(filePathsToIgnore)
98+
.map((ignore) => path.join(workspaceUri.fsPath, ignore)),
99+
})
100+
.on('add', (path) => this.updateDocument(path))
101+
.on('unlink', (path) => this.removeDocument(path))
102+
.on('change', (path) => this.updateDocument(path));
70103
}
71104

72-
writer.completion(result.fileCount, result.errorCount, result.warningCount);
105+
private updateDocument(path: string) {
106+
const text = fs.readFileSync(path, 'utf-8');
107+
this.svelteCheck.upsertDocument({ text, uri: URI.file(path).toString() });
108+
this.scheduleDiagnostics();
109+
}
73110

74-
return result;
111+
private removeDocument(path: string) {
112+
this.svelteCheck.removeDocument(URI.file(path).toString());
113+
this.scheduleDiagnostics();
114+
}
115+
116+
private scheduleDiagnostics() {
117+
clearTimeout(this.updateDiagnostics);
118+
this.updateDiagnostics = setTimeout(
119+
() => getDiagnostics(this.workspaceUri, this.writer, this.svelteCheck),
120+
1000,
121+
);
122+
}
75123
}
76124

77125
(async () => {
@@ -99,12 +147,23 @@ async function getDiagnostics(workspaceUri: URI, writer: Writer): Promise<Result
99147
writer = new MachineFriendlyWriter(process.stdout);
100148
}
101149

102-
const result = await getDiagnostics(workspaceUri, writer);
150+
const svelteCheck = new SvelteCheck(workspaceUri.fsPath);
151+
const filePathsToIgnore = myArgs['ignore'].split(' ') || [];
103152

104-
if (result && (result as Result).errorCount === 0) {
105-
process.exit(0);
153+
if (myArgs['watch']) {
154+
new DiagnosticsWatcher(workspaceUri, svelteCheck, writer, filePathsToIgnore);
106155
} else {
107-
process.exit(1);
156+
openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck);
157+
const result = await getDiagnostics(workspaceUri, writer, svelteCheck);
158+
if (
159+
result &&
160+
result.errorCount === 0 &&
161+
(!myArgs['fail-on-warnings'] || result.warningCount === 0)
162+
) {
163+
process.exit(0);
164+
} else {
165+
process.exit(1);
166+
}
108167
}
109168
})().catch((_err) => {
110169
console.error(_err);

0 commit comments

Comments
 (0)