diff options
-rw-r--r-- | .github/workflows/release.yaml | 1 | ||||
-rw-r--r-- | Cargo.lock | 23 | ||||
-rw-r--r-- | editors/code/src/commands/server_version.ts | 2 | ||||
-rw-r--r-- | editors/code/src/config.ts | 41 | ||||
-rw-r--r-- | editors/code/src/ctx.ts | 6 | ||||
-rw-r--r-- | editors/code/src/installation/extension.ts | 16 | ||||
-rw-r--r-- | editors/code/src/installation/server.ts | 21 | ||||
-rw-r--r-- | editors/code/src/main.ts | 10 | ||||
-rw-r--r-- | editors/code/src/persistent_state.ts | 49 | ||||
-rw-r--r-- | xtask/tests/tidy-tests/cli.rs | 25 | ||||
-rw-r--r-- | xtask/tests/tidy-tests/docs.rs | 106 | ||||
-rw-r--r-- | xtask/tests/tidy-tests/main.rs | 145 |
12 files changed, 233 insertions, 212 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0434b6128..21ac3a4bc 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml | |||
@@ -11,6 +11,7 @@ jobs: | |||
11 | dist: | 11 | dist: |
12 | name: dist | 12 | name: dist |
13 | runs-on: ${{ matrix.os }} | 13 | runs-on: ${{ matrix.os }} |
14 | if: github.repository == "rust-analyzer/rust-analyzer" | ||
14 | strategy: | 15 | strategy: |
15 | matrix: | 16 | matrix: |
16 | os: [ubuntu-latest, windows-latest, macos-latest] | 17 | os: [ubuntu-latest, windows-latest, macos-latest] |
diff --git a/Cargo.lock b/Cargo.lock index efe8dd189..f6df77206 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -419,9 +419,9 @@ dependencies = [ | |||
419 | 419 | ||
420 | [[package]] | 420 | [[package]] |
421 | name = "globset" | 421 | name = "globset" |
422 | version = "0.4.4" | 422 | version = "0.4.5" |
423 | source = "registry+https://github.com/rust-lang/crates.io-index" | 423 | source = "registry+https://github.com/rust-lang/crates.io-index" |
424 | checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" | 424 | checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" |
425 | dependencies = [ | 425 | dependencies = [ |
426 | "aho-corasick", | 426 | "aho-corasick", |
427 | "bstr", | 427 | "bstr", |
@@ -668,11 +668,11 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" | |||
668 | 668 | ||
669 | [[package]] | 669 | [[package]] |
670 | name = "memoffset" | 670 | name = "memoffset" |
671 | version = "0.5.3" | 671 | version = "0.5.4" |
672 | source = "registry+https://github.com/rust-lang/crates.io-index" | 672 | source = "registry+https://github.com/rust-lang/crates.io-index" |
673 | checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" | 673 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" |
674 | dependencies = [ | 674 | dependencies = [ |
675 | "rustc_version", | 675 | "autocfg", |
676 | ] | 676 | ] |
677 | 677 | ||
678 | [[package]] | 678 | [[package]] |
@@ -1133,9 +1133,9 @@ dependencies = [ | |||
1133 | 1133 | ||
1134 | [[package]] | 1134 | [[package]] |
1135 | name = "ra_vfs" | 1135 | name = "ra_vfs" |
1136 | version = "0.5.2" | 1136 | version = "0.5.3" |
1137 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1138 | checksum = "bc898f237e4b4498959ae0100c688793a23e77624d44ef710ba70094217f98e0" | 1138 | checksum = "58a265769d5e5655345a9fcbd870a1a7c3658558c0d8efaed79e0669358f46b8" |
1139 | dependencies = [ | 1139 | dependencies = [ |
1140 | "crossbeam-channel", | 1140 | "crossbeam-channel", |
1141 | "jod-thread", | 1141 | "jod-thread", |
@@ -1332,15 +1332,6 @@ dependencies = [ | |||
1332 | ] | 1332 | ] |
1333 | 1333 | ||
1334 | [[package]] | 1334 | [[package]] |
1335 | name = "rustc_version" | ||
1336 | version = "0.2.3" | ||
1337 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1338 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" | ||
1339 | dependencies = [ | ||
1340 | "semver", | ||
1341 | ] | ||
1342 | |||
1343 | [[package]] | ||
1344 | name = "ryu" | 1335 | name = "ryu" |
1345 | version = "1.0.3" | 1336 | version = "1.0.3" |
1346 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" |
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts index c4d84b443..83b1acf67 100644 --- a/editors/code/src/commands/server_version.ts +++ b/editors/code/src/commands/server_version.ts | |||
@@ -5,7 +5,7 @@ import { spawnSync } from 'child_process'; | |||
5 | 5 | ||
6 | export function serverVersion(ctx: Ctx): Cmd { | 6 | export function serverVersion(ctx: Ctx): Cmd { |
7 | return async () => { | 7 | return async () => { |
8 | const binaryPath = await ensureServerBinary(ctx.config); | 8 | const binaryPath = await ensureServerBinary(ctx.config, ctx.state); |
9 | 9 | ||
10 | if (binaryPath == null) { | 10 | if (binaryPath == null) { |
11 | throw new Error( | 11 | throw new Error( |
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index f63e1d20e..bd8096dd6 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -182,13 +182,6 @@ export class Config { | |||
182 | return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); | 182 | return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG); |
183 | } | 183 | } |
184 | 184 | ||
185 | readonly installedNightlyExtensionReleaseDate = new DateStorage( | ||
186 | "installed-nightly-extension-release-date", | ||
187 | this.ctx.globalState | ||
188 | ); | ||
189 | readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); | ||
190 | readonly serverReleaseTag = new Storage<null | string>("server-release-tag", this.ctx.globalState, null); | ||
191 | |||
192 | // We don't do runtime config validation here for simplicity. More on stackoverflow: | 185 | // We don't do runtime config validation here for simplicity. More on stackoverflow: |
193 | // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension | 186 | // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension |
194 | 187 | ||
@@ -232,37 +225,3 @@ export class Config { | |||
232 | // for internal use | 225 | // for internal use |
233 | get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } | 226 | get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } |
234 | } | 227 | } |
235 | |||
236 | export class Storage<T> { | ||
237 | constructor( | ||
238 | private readonly key: string, | ||
239 | private readonly storage: vscode.Memento, | ||
240 | private readonly defaultVal: T | ||
241 | ) { } | ||
242 | |||
243 | get(): T { | ||
244 | const val = this.storage.get(this.key, this.defaultVal); | ||
245 | log.debug(this.key, "==", val); | ||
246 | return val; | ||
247 | } | ||
248 | async set(val: T) { | ||
249 | log.debug(this.key, "=", val); | ||
250 | await this.storage.update(this.key, val); | ||
251 | } | ||
252 | } | ||
253 | export class DateStorage { | ||
254 | inner: Storage<null | string>; | ||
255 | |||
256 | constructor(key: string, storage: vscode.Memento) { | ||
257 | this.inner = new Storage(key, storage, null); | ||
258 | } | ||
259 | |||
260 | get(): null | Date { | ||
261 | const dateStr = this.inner.get(); | ||
262 | return dateStr ? new Date(dateStr) : null; | ||
263 | } | ||
264 | |||
265 | async set(date: null | Date) { | ||
266 | await this.inner.set(date ? date.toString() : null); | ||
267 | } | ||
268 | } | ||
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 25ef38aed..c929ab063 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts | |||
@@ -4,19 +4,21 @@ import * as lc from 'vscode-languageclient'; | |||
4 | import { Config } from './config'; | 4 | import { Config } from './config'; |
5 | import { createClient } from './client'; | 5 | import { createClient } from './client'; |
6 | import { isRustEditor, RustEditor } from './util'; | 6 | import { isRustEditor, RustEditor } from './util'; |
7 | import { PersistentState } from './persistent_state'; | ||
7 | 8 | ||
8 | export class Ctx { | 9 | export class Ctx { |
9 | private constructor( | 10 | private constructor( |
10 | readonly config: Config, | 11 | readonly config: Config, |
12 | readonly state: PersistentState, | ||
11 | private readonly extCtx: vscode.ExtensionContext, | 13 | private readonly extCtx: vscode.ExtensionContext, |
12 | readonly client: lc.LanguageClient | 14 | readonly client: lc.LanguageClient |
13 | ) { | 15 | ) { |
14 | 16 | ||
15 | } | 17 | } |
16 | 18 | ||
17 | static async create(config: Config, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> { | 19 | static async create(config: Config, state: PersistentState, extCtx: vscode.ExtensionContext, serverPath: string): Promise<Ctx> { |
18 | const client = await createClient(config, serverPath); | 20 | const client = await createClient(config, serverPath); |
19 | const res = new Ctx(config, extCtx, client); | 21 | const res = new Ctx(config, state, extCtx, client); |
20 | res.pushCleanup(client.start()); | 22 | res.pushCleanup(client.start()); |
21 | await client.onReady(); | 23 | await client.onReady(); |
22 | return res; | 24 | return res; |
diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts index eea6fded2..a1db96f05 100644 --- a/editors/code/src/installation/extension.ts +++ b/editors/code/src/installation/extension.ts | |||
@@ -7,6 +7,7 @@ import { Config, UpdatesChannel } from "../config"; | |||
7 | import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; | 7 | import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces"; |
8 | import { downloadArtifactWithProgressUi } from "./downloads"; | 8 | import { downloadArtifactWithProgressUi } from "./downloads"; |
9 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; | 9 | import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; |
10 | import { PersistentState } from "../persistent_state"; | ||
10 | 11 | ||
11 | const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; | 12 | const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; |
12 | 13 | ||
@@ -14,7 +15,7 @@ const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25; | |||
14 | * Installs `stable` or latest `nightly` version or does nothing if the current | 15 | * Installs `stable` or latest `nightly` version or does nothing if the current |
15 | * extension version is what's needed according to `desiredUpdateChannel`. | 16 | * extension version is what's needed according to `desiredUpdateChannel`. |
16 | */ | 17 | */ |
17 | export async function ensureProperExtensionVersion(config: Config): Promise<never | void> { | 18 | export async function ensureProperExtensionVersion(config: Config, state: PersistentState): Promise<never | void> { |
18 | // User has built lsp server from sources, she should manage updates manually | 19 | // User has built lsp server from sources, she should manage updates manually |
19 | if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; | 20 | if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return; |
20 | 21 | ||
@@ -23,7 +24,7 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve | |||
23 | 24 | ||
24 | if (currentUpdChannel === UpdatesChannel.Stable) { | 25 | if (currentUpdChannel === UpdatesChannel.Stable) { |
25 | // Release date is present only when we are on nightly | 26 | // Release date is present only when we are on nightly |
26 | await config.installedNightlyExtensionReleaseDate.set(null); | 27 | await state.installedNightlyExtensionReleaseDate.set(null); |
27 | } | 28 | } |
28 | 29 | ||
29 | if (desiredUpdChannel === UpdatesChannel.Stable) { | 30 | if (desiredUpdChannel === UpdatesChannel.Stable) { |
@@ -39,10 +40,10 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve | |||
39 | if (currentUpdChannel === UpdatesChannel.Stable) { | 40 | if (currentUpdChannel === UpdatesChannel.Stable) { |
40 | if (!await askToDownloadProperExtensionVersion(config)) return; | 41 | if (!await askToDownloadProperExtensionVersion(config)) return; |
41 | 42 | ||
42 | return await tryDownloadNightlyExtension(config); | 43 | return await tryDownloadNightlyExtension(config, state); |
43 | } | 44 | } |
44 | 45 | ||
45 | const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get(); | 46 | const currentExtReleaseDate = state.installedNightlyExtensionReleaseDate.get(); |
46 | 47 | ||
47 | if (currentExtReleaseDate === null) { | 48 | if (currentExtReleaseDate === null) { |
48 | void vscode.window.showErrorMessage( | 49 | void vscode.window.showErrorMessage( |
@@ -66,9 +67,9 @@ export async function ensureProperExtensionVersion(config: Config): Promise<neve | |||
66 | return; | 67 | return; |
67 | } | 68 | } |
68 | 69 | ||
69 | await tryDownloadNightlyExtension(config, releaseInfo => { | 70 | await tryDownloadNightlyExtension(config, state, releaseInfo => { |
70 | assert( | 71 | assert( |
71 | currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(), | 72 | currentExtReleaseDate.getTime() === state.installedNightlyExtensionReleaseDate.get()?.getTime(), |
72 | "Other active VSCode instance has reinstalled the extension" | 73 | "Other active VSCode instance has reinstalled the extension" |
73 | ); | 74 | ); |
74 | 75 | ||
@@ -111,6 +112,7 @@ async function askToDownloadProperExtensionVersion(config: Config, reason = "") | |||
111 | */ | 112 | */ |
112 | const tryDownloadNightlyExtension = notReentrant(async ( | 113 | const tryDownloadNightlyExtension = notReentrant(async ( |
113 | config: Config, | 114 | config: Config, |
115 | state: PersistentState, | ||
114 | shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true | 116 | shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true |
115 | ): Promise<never | void> => { | 117 | ): Promise<never | void> => { |
116 | const vsixSource = config.nightlyVsixSource; | 118 | const vsixSource = config.nightlyVsixSource; |
@@ -124,7 +126,7 @@ const tryDownloadNightlyExtension = notReentrant(async ( | |||
124 | const vsixPath = path.join(vsixSource.dir, vsixSource.file); | 126 | const vsixPath = path.join(vsixSource.dir, vsixSource.file); |
125 | 127 | ||
126 | await vscodeInstallExtensionFromVsix(vsixPath); | 128 | await vscodeInstallExtensionFromVsix(vsixPath); |
127 | await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); | 129 | await state.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate); |
128 | await fs.unlink(vsixPath); | 130 | await fs.unlink(vsixPath); |
129 | 131 | ||
130 | await vscodeReloadWindow(); // never returns | 132 | await vscodeReloadWindow(); // never returns |
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts index 05730a778..05d326131 100644 --- a/editors/code/src/installation/server.ts +++ b/editors/code/src/installation/server.ts | |||
@@ -7,8 +7,9 @@ import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; | |||
7 | import { downloadArtifactWithProgressUi } from "./downloads"; | 7 | import { downloadArtifactWithProgressUi } from "./downloads"; |
8 | import { log, assert, notReentrant } from "../util"; | 8 | import { log, assert, notReentrant } from "../util"; |
9 | import { Config, NIGHTLY_TAG } from "../config"; | 9 | import { Config, NIGHTLY_TAG } from "../config"; |
10 | import { PersistentState } from "../persistent_state"; | ||
10 | 11 | ||
11 | export async function ensureServerBinary(config: Config): Promise<null | string> { | 12 | export async function ensureServerBinary(config: Config, state: PersistentState): Promise<null | string> { |
12 | const source = config.serverSource; | 13 | const source = config.serverSource; |
13 | 14 | ||
14 | if (!source) { | 15 | if (!source) { |
@@ -37,7 +38,7 @@ export async function ensureServerBinary(config: Config): Promise<null | string> | |||
37 | return null; | 38 | return null; |
38 | } | 39 | } |
39 | case ArtifactSource.Type.GithubRelease: { | 40 | case ArtifactSource.Type.GithubRelease: { |
40 | if (!shouldDownloadServer(source, config)) { | 41 | if (!shouldDownloadServer(state, source)) { |
41 | return path.join(source.dir, source.file); | 42 | return path.join(source.dir, source.file); |
42 | } | 43 | } |
43 | 44 | ||
@@ -50,24 +51,24 @@ export async function ensureServerBinary(config: Config): Promise<null | string> | |||
50 | if (userResponse !== "Download now") return null; | 51 | if (userResponse !== "Download now") return null; |
51 | } | 52 | } |
52 | 53 | ||
53 | return await downloadServer(source, config); | 54 | return await downloadServer(state, source); |
54 | } | 55 | } |
55 | } | 56 | } |
56 | } | 57 | } |
57 | 58 | ||
58 | function shouldDownloadServer( | 59 | function shouldDownloadServer( |
60 | state: PersistentState, | ||
59 | source: ArtifactSource.GithubRelease, | 61 | source: ArtifactSource.GithubRelease, |
60 | config: Config | ||
61 | ): boolean { | 62 | ): boolean { |
62 | if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; | 63 | if (!isBinaryAvailable(path.join(source.dir, source.file))) return true; |
63 | 64 | ||
64 | const installed = { | 65 | const installed = { |
65 | tag: config.serverReleaseTag.get(), | 66 | tag: state.serverReleaseTag.get(), |
66 | date: config.serverReleaseDate.get() | 67 | date: state.serverReleaseDate.get() |
67 | }; | 68 | }; |
68 | const required = { | 69 | const required = { |
69 | tag: source.tag, | 70 | tag: source.tag, |
70 | date: config.installedNightlyExtensionReleaseDate.get() | 71 | date: state.installedNightlyExtensionReleaseDate.get() |
71 | }; | 72 | }; |
72 | 73 | ||
73 | log.debug("Installed server:", installed, "required:", required); | 74 | log.debug("Installed server:", installed, "required:", required); |
@@ -86,16 +87,16 @@ function shouldDownloadServer( | |||
86 | * Enforcing no reentrancy for this is best-effort. | 87 | * Enforcing no reentrancy for this is best-effort. |
87 | */ | 88 | */ |
88 | const downloadServer = notReentrant(async ( | 89 | const downloadServer = notReentrant(async ( |
90 | state: PersistentState, | ||
89 | source: ArtifactSource.GithubRelease, | 91 | source: ArtifactSource.GithubRelease, |
90 | config: Config, | ||
91 | ): Promise<null | string> => { | 92 | ): Promise<null | string> => { |
92 | try { | 93 | try { |
93 | const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); | 94 | const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); |
94 | 95 | ||
95 | await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); | 96 | await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server"); |
96 | await Promise.all([ | 97 | await Promise.all([ |
97 | config.serverReleaseTag.set(releaseInfo.releaseName), | 98 | state.serverReleaseTag.set(releaseInfo.releaseName), |
98 | config.serverReleaseDate.set(releaseInfo.releaseDate) | 99 | state.serverReleaseDate.set(releaseInfo.releaseDate) |
99 | ]); | 100 | ]); |
100 | } catch (err) { | 101 | } catch (err) { |
101 | log.downloadError(err, "language server", source.repo.name); | 102 | log.downloadError(err, "language server", source.repo.name); |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index bd4661a36..94ecd4dab 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -9,6 +9,7 @@ import { ensureServerBinary } from './installation/server'; | |||
9 | import { Config } from './config'; | 9 | import { Config } from './config'; |
10 | import { log } from './util'; | 10 | import { log } from './util'; |
11 | import { ensureProperExtensionVersion } from './installation/extension'; | 11 | import { ensureProperExtensionVersion } from './installation/extension'; |
12 | import { PersistentState } from './persistent_state'; | ||
12 | 13 | ||
13 | let ctx: Ctx | undefined; | 14 | let ctx: Ctx | undefined; |
14 | 15 | ||
@@ -34,13 +35,14 @@ export async function activate(context: vscode.ExtensionContext) { | |||
34 | context.subscriptions.push(defaultOnEnter); | 35 | context.subscriptions.push(defaultOnEnter); |
35 | 36 | ||
36 | const config = new Config(context); | 37 | const config = new Config(context); |
38 | const state = new PersistentState(context); | ||
37 | 39 | ||
38 | vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error)); | 40 | vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error)); |
39 | 41 | ||
40 | // Don't await the user response here, otherwise we will block the lsp server bootstrap | 42 | // Don't await the user response here, otherwise we will block the lsp server bootstrap |
41 | void ensureProperExtensionVersion(config).catch(log.error); | 43 | void ensureProperExtensionVersion(config, state).catch(log.error); |
42 | 44 | ||
43 | const serverPath = await ensureServerBinary(config); | 45 | const serverPath = await ensureServerBinary(config, state); |
44 | 46 | ||
45 | if (serverPath == null) { | 47 | if (serverPath == null) { |
46 | throw new Error( | 48 | throw new Error( |
@@ -53,7 +55,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
53 | // registers its `onDidChangeDocument` handler before us. | 55 | // registers its `onDidChangeDocument` handler before us. |
54 | // | 56 | // |
55 | // This a horribly, horribly wrong way to deal with this problem. | 57 | // This a horribly, horribly wrong way to deal with this problem. |
56 | ctx = await Ctx.create(config, context, serverPath); | 58 | ctx = await Ctx.create(config, state, context, serverPath); |
57 | 59 | ||
58 | // Commands which invokes manually via command palette, shortcut, etc. | 60 | // Commands which invokes manually via command palette, shortcut, etc. |
59 | ctx.registerCommand('reload', (ctx) => { | 61 | ctx.registerCommand('reload', (ctx) => { |
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts new file mode 100644 index 000000000..13095b806 --- /dev/null +++ b/editors/code/src/persistent_state.ts | |||
@@ -0,0 +1,49 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import { log } from "./util"; | ||
3 | |||
4 | export class PersistentState { | ||
5 | constructor(private readonly ctx: vscode.ExtensionContext) { | ||
6 | } | ||
7 | |||
8 | readonly installedNightlyExtensionReleaseDate = new DateStorage( | ||
9 | "installed-nightly-extension-release-date", | ||
10 | this.ctx.globalState | ||
11 | ); | ||
12 | readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState); | ||
13 | readonly serverReleaseTag = new Storage<null | string>("server-release-tag", this.ctx.globalState, null); | ||
14 | } | ||
15 | |||
16 | |||
17 | export class Storage<T> { | ||
18 | constructor( | ||
19 | private readonly key: string, | ||
20 | private readonly storage: vscode.Memento, | ||
21 | private readonly defaultVal: T | ||
22 | ) { } | ||
23 | |||
24 | get(): T { | ||
25 | const val = this.storage.get(this.key, this.defaultVal); | ||
26 | log.debug(this.key, "==", val); | ||
27 | return val; | ||
28 | } | ||
29 | async set(val: T) { | ||
30 | log.debug(this.key, "=", val); | ||
31 | await this.storage.update(this.key, val); | ||
32 | } | ||
33 | } | ||
34 | export class DateStorage { | ||
35 | inner: Storage<null | string>; | ||
36 | |||
37 | constructor(key: string, storage: vscode.Memento) { | ||
38 | this.inner = new Storage(key, storage, null); | ||
39 | } | ||
40 | |||
41 | get(): null | Date { | ||
42 | const dateStr = this.inner.get(); | ||
43 | return dateStr ? new Date(dateStr) : null; | ||
44 | } | ||
45 | |||
46 | async set(date: null | Date) { | ||
47 | await this.inner.set(date ? date.toString() : null); | ||
48 | } | ||
49 | } | ||
diff --git a/xtask/tests/tidy-tests/cli.rs b/xtask/tests/tidy-tests/cli.rs index f9ca45292..f5b00a8b8 100644 --- a/xtask/tests/tidy-tests/cli.rs +++ b/xtask/tests/tidy-tests/cli.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | use walkdir::WalkDir; | ||
2 | use xtask::{ | 1 | use xtask::{ |
3 | codegen::{self, Mode}, | 2 | codegen::{self, Mode}, |
4 | project_root, run_rustfmt, | 3 | run_rustfmt, |
5 | }; | 4 | }; |
6 | 5 | ||
7 | #[test] | 6 | #[test] |
@@ -31,25 +30,3 @@ fn check_code_formatting() { | |||
31 | panic!("{}. Please format the code by running `cargo format`", error); | 30 | panic!("{}. Please format the code by running `cargo format`", error); |
32 | } | 31 | } |
33 | } | 32 | } |
34 | |||
35 | #[test] | ||
36 | fn no_todo() { | ||
37 | WalkDir::new(project_root().join("crates")).into_iter().for_each(|e| { | ||
38 | let e = e.unwrap(); | ||
39 | if e.path().extension().map(|it| it != "rs").unwrap_or(true) { | ||
40 | return; | ||
41 | } | ||
42 | if e.path().ends_with("tests/cli.rs") { | ||
43 | return; | ||
44 | } | ||
45 | let text = std::fs::read_to_string(e.path()).unwrap(); | ||
46 | if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") { | ||
47 | panic!( | ||
48 | "\nTODO markers should not be committed to the master branch,\n\ | ||
49 | use FIXME instead\n\ | ||
50 | {}\n", | ||
51 | e.path().display(), | ||
52 | ) | ||
53 | } | ||
54 | }) | ||
55 | } | ||
diff --git a/xtask/tests/tidy-tests/docs.rs b/xtask/tests/tidy-tests/docs.rs deleted file mode 100644 index 62c4f8441..000000000 --- a/xtask/tests/tidy-tests/docs.rs +++ /dev/null | |||
@@ -1,106 +0,0 @@ | |||
1 | use std::{collections::HashMap, fs, io::prelude::*, io::BufReader, path::Path}; | ||
2 | |||
3 | use anyhow::Context; | ||
4 | use walkdir::{DirEntry, WalkDir}; | ||
5 | use xtask::project_root; | ||
6 | |||
7 | fn is_exclude_dir(p: &Path) -> bool { | ||
8 | // Test hopefully don't really need comments, and for assists we already | ||
9 | // have special comments which are source of doc tests and user docs. | ||
10 | let exclude_dirs = ["tests", "test_data", "handlers"]; | ||
11 | let mut cur_path = p; | ||
12 | while let Some(path) = cur_path.parent() { | ||
13 | if exclude_dirs.iter().any(|dir| path.ends_with(dir)) { | ||
14 | return true; | ||
15 | } | ||
16 | cur_path = path; | ||
17 | } | ||
18 | |||
19 | false | ||
20 | } | ||
21 | |||
22 | fn is_exclude_file(d: &DirEntry) -> bool { | ||
23 | let file_names = ["tests.rs"]; | ||
24 | |||
25 | d.file_name().to_str().map(|f_n| file_names.iter().any(|name| *name == f_n)).unwrap_or(false) | ||
26 | } | ||
27 | |||
28 | fn is_hidden(entry: &DirEntry) -> bool { | ||
29 | entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) | ||
30 | } | ||
31 | |||
32 | #[test] | ||
33 | fn no_docs_comments() { | ||
34 | let crates = project_root().join("crates"); | ||
35 | let iter = WalkDir::new(crates); | ||
36 | let mut missing_docs = Vec::new(); | ||
37 | let mut contains_fixme = Vec::new(); | ||
38 | for f in iter.into_iter().filter_entry(|e| !is_hidden(e)) { | ||
39 | let f = f.unwrap(); | ||
40 | if f.file_type().is_dir() { | ||
41 | continue; | ||
42 | } | ||
43 | if f.path().extension().map(|it| it != "rs").unwrap_or(false) { | ||
44 | continue; | ||
45 | } | ||
46 | if is_exclude_dir(f.path()) { | ||
47 | continue; | ||
48 | } | ||
49 | if is_exclude_file(&f) { | ||
50 | continue; | ||
51 | } | ||
52 | let mut reader = BufReader::new(fs::File::open(f.path()).unwrap()); | ||
53 | let mut line = String::new(); | ||
54 | reader | ||
55 | .read_line(&mut line) | ||
56 | .with_context(|| format!("Failed to read {}", f.path().display())) | ||
57 | .unwrap(); | ||
58 | |||
59 | if line.starts_with("//!") { | ||
60 | if line.contains("FIXME") { | ||
61 | contains_fixme.push(f.path().to_path_buf()) | ||
62 | } | ||
63 | } else { | ||
64 | missing_docs.push(f.path().display().to_string()); | ||
65 | } | ||
66 | } | ||
67 | if !missing_docs.is_empty() { | ||
68 | panic!( | ||
69 | "\nMissing docs strings\n\n\ | ||
70 | modules:\n{}\n\n", | ||
71 | missing_docs.join("\n") | ||
72 | ) | ||
73 | } | ||
74 | |||
75 | let whitelist = [ | ||
76 | "ra_db", | ||
77 | "ra_hir", | ||
78 | "ra_hir_expand", | ||
79 | "ra_ide", | ||
80 | "ra_mbe", | ||
81 | "ra_parser", | ||
82 | "ra_prof", | ||
83 | "ra_project_model", | ||
84 | "ra_syntax", | ||
85 | "ra_text_edit", | ||
86 | "ra_tt", | ||
87 | "ra_hir_ty", | ||
88 | ]; | ||
89 | |||
90 | let mut has_fixmes = whitelist.iter().map(|it| (*it, false)).collect::<HashMap<&str, bool>>(); | ||
91 | 'outer: for path in contains_fixme { | ||
92 | for krate in whitelist.iter() { | ||
93 | if path.components().any(|it| it.as_os_str() == *krate) { | ||
94 | has_fixmes.insert(krate, true); | ||
95 | continue 'outer; | ||
96 | } | ||
97 | } | ||
98 | panic!("FIXME doc in a fully-documented crate: {}", path.display()) | ||
99 | } | ||
100 | |||
101 | for (krate, has_fixme) in has_fixmes.iter() { | ||
102 | if !has_fixme { | ||
103 | panic!("crate {} is fully documented, remove it from the white list", krate) | ||
104 | } | ||
105 | } | ||
106 | } | ||
diff --git a/xtask/tests/tidy-tests/main.rs b/xtask/tests/tidy-tests/main.rs index 56d1318d6..2d2d88bec 100644 --- a/xtask/tests/tidy-tests/main.rs +++ b/xtask/tests/tidy-tests/main.rs | |||
@@ -1,2 +1,145 @@ | |||
1 | mod cli; | 1 | mod cli; |
2 | mod docs; | 2 | |
3 | use std::{ | ||
4 | collections::HashMap, | ||
5 | path::{Path, PathBuf}, | ||
6 | }; | ||
7 | |||
8 | use walkdir::{DirEntry, WalkDir}; | ||
9 | use xtask::{not_bash::fs2, project_root}; | ||
10 | |||
11 | #[test] | ||
12 | fn rust_files_are_tidy() { | ||
13 | let mut tidy_docs = TidyDocs::default(); | ||
14 | for path in rust_files() { | ||
15 | let text = fs2::read_to_string(&path).unwrap(); | ||
16 | check_todo(&path, &text); | ||
17 | tidy_docs.visit(&path, &text); | ||
18 | } | ||
19 | tidy_docs.finish(); | ||
20 | } | ||
21 | |||
22 | fn check_todo(path: &Path, text: &str) { | ||
23 | if path.ends_with("tests/cli.rs") { | ||
24 | return; | ||
25 | } | ||
26 | if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") { | ||
27 | panic!( | ||
28 | "\nTODO markers should not be committed to the master branch,\n\ | ||
29 | use FIXME instead\n\ | ||
30 | {}\n", | ||
31 | path.display(), | ||
32 | ) | ||
33 | } | ||
34 | } | ||
35 | |||
36 | #[derive(Default)] | ||
37 | struct TidyDocs { | ||
38 | missing_docs: Vec<String>, | ||
39 | contains_fixme: Vec<PathBuf>, | ||
40 | } | ||
41 | |||
42 | impl TidyDocs { | ||
43 | fn visit(&mut self, path: &Path, text: &str) { | ||
44 | if is_exclude_dir(path) || is_exclude_file(path) { | ||
45 | return; | ||
46 | } | ||
47 | |||
48 | let first_line = match text.lines().next() { | ||
49 | Some(it) => it, | ||
50 | None => return, | ||
51 | }; | ||
52 | |||
53 | if first_line.starts_with("//!") { | ||
54 | if first_line.contains("FIXME") { | ||
55 | self.contains_fixme.push(path.to_path_buf()) | ||
56 | } | ||
57 | } else { | ||
58 | self.missing_docs.push(path.display().to_string()); | ||
59 | } | ||
60 | |||
61 | fn is_exclude_dir(p: &Path) -> bool { | ||
62 | // Test hopefully don't really need comments, and for assists we already | ||
63 | // have special comments which are source of doc tests and user docs. | ||
64 | let exclude_dirs = ["tests", "test_data", "handlers"]; | ||
65 | let mut cur_path = p; | ||
66 | while let Some(path) = cur_path.parent() { | ||
67 | if exclude_dirs.iter().any(|dir| path.ends_with(dir)) { | ||
68 | return true; | ||
69 | } | ||
70 | cur_path = path; | ||
71 | } | ||
72 | |||
73 | false | ||
74 | } | ||
75 | |||
76 | fn is_exclude_file(d: &Path) -> bool { | ||
77 | let file_names = ["tests.rs"]; | ||
78 | |||
79 | d.file_name() | ||
80 | .unwrap_or_default() | ||
81 | .to_str() | ||
82 | .map(|f_n| file_names.iter().any(|name| *name == f_n)) | ||
83 | .unwrap_or(false) | ||
84 | } | ||
85 | } | ||
86 | |||
87 | fn finish(self) { | ||
88 | if !self.missing_docs.is_empty() { | ||
89 | panic!( | ||
90 | "\nMissing docs strings\n\n\ | ||
91 | modules:\n{}\n\n", | ||
92 | self.missing_docs.join("\n") | ||
93 | ) | ||
94 | } | ||
95 | |||
96 | let whitelist = [ | ||
97 | "ra_db", | ||
98 | "ra_hir", | ||
99 | "ra_hir_expand", | ||
100 | "ra_ide", | ||
101 | "ra_mbe", | ||
102 | "ra_parser", | ||
103 | "ra_prof", | ||
104 | "ra_project_model", | ||
105 | "ra_syntax", | ||
106 | "ra_text_edit", | ||
107 | "ra_tt", | ||
108 | "ra_hir_ty", | ||
109 | ]; | ||
110 | |||
111 | let mut has_fixmes = | ||
112 | whitelist.iter().map(|it| (*it, false)).collect::<HashMap<&str, bool>>(); | ||
113 | 'outer: for path in self.contains_fixme { | ||
114 | for krate in whitelist.iter() { | ||
115 | if path.components().any(|it| it.as_os_str() == *krate) { | ||
116 | has_fixmes.insert(krate, true); | ||
117 | continue 'outer; | ||
118 | } | ||
119 | } | ||
120 | panic!("FIXME doc in a fully-documented crate: {}", path.display()) | ||
121 | } | ||
122 | |||
123 | for (krate, has_fixme) in has_fixmes.iter() { | ||
124 | if !has_fixme { | ||
125 | panic!("crate {} is fully documented, remove it from the white list", krate) | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | fn rust_files() -> impl Iterator<Item = PathBuf> { | ||
132 | let crates = project_root().join("crates"); | ||
133 | let iter = WalkDir::new(crates); | ||
134 | return iter | ||
135 | .into_iter() | ||
136 | .filter_entry(|e| !is_hidden(e)) | ||
137 | .map(|e| e.unwrap()) | ||
138 | .filter(|e| !e.file_type().is_dir()) | ||
139 | .map(|e| e.into_path()) | ||
140 | .filter(|path| path.extension().map(|it| it == "rs").unwrap_or(false)); | ||
141 | |||
142 | fn is_hidden(entry: &DirEntry) -> bool { | ||
143 | entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) | ||
144 | } | ||
145 | } | ||