diff --git a/CHANGELOG.md b/CHANGELOG.md index 826f1b4..391c698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ All notable changes to the Official Dotenv VS Code extension will be documented in this file. -## [Unreleased](https://github.com/dotenvx/dotenv-vscode/compare/v1.5.7...master) +## [Unreleased](https://github.com/dotenvx/dotenv-vscode/compare/v1.5.8...master) + +## [1.5.8](https://github.com/dotenvx/dotenv-vscode/compare/v1.5.7...v1.5.8) (2026-09-23) + +### Changed + +* Get settings right - turning off the feature not the cloaking. ([#143](https://github.com/dotenvx/dotenv-vscode/pull/143)) ## [1.5.7](https://github.com/dotenvx/dotenv-vscode/compare/v1.5.6...v1.5.7) (2026-09-23) @@ -183,6 +189,8 @@ All notable changes to the Official Dotenv VS Code extension will be documented ### Fixed +* Always cloak new secret peeks, independently of editor cloaking. Disable cloaking controls when the auto-cloaking feature is turned off. + * Disable dotenv hovers and expanded autocomplete value details when secret peeking is turned off, while keeping autocomplete suggestions available. * Reverted code causing autocloaking to fail [#93](https://github.com/dotenvx/dotenv-vscode/pull/93) diff --git a/README.md b/README.md index 20a1541..7832dca 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ from the Command Palette to reveal or hide them. Switching tabs hides them again in the Dotenv Editor when auto-cloaking is enabled. Set `dotenv.enableAutocloaking` to `false` to keep values visible, including after -reopening files or switching tabs. Changes to this setting apply to open editors immediately. +reopening files or switching tabs. This disables the cloaking feature and removes +its toggle controls. Changes apply to open editors immediately.   @@ -96,7 +97,9 @@ secret-peeking setting stays unchanged. Hover over a reference such as `process.env.SECRET_KEY` or `ENV["SECRET_KEY"]`. -The popup shows the source file and lets you **Reveal value** or **Hide value**. +Every new hover or expanded autocomplete peek starts cloaked. Click **Reveal value** +to reveal that popup, or **Hide value** to cloak it again. Editor cloaking toggles +never reveal peek values. Uncheck **Dotenv: Enable Secretpeeking** to disable dotenv hovers and in-code Reveal actions, including the expanded value details in autocomplete. Completion suggestions remain available with masked values. diff --git a/lib/autocloaking.js b/lib/autocloaking.js index 3954f25..06d09c8 100644 --- a/lib/autocloaking.js +++ b/lib/autocloaking.js @@ -58,6 +58,7 @@ function decorateMasking (context) { const toggleLink = { provideCodeLenses: function (document, token) { + if (!settings.autocloakingFeatureEnabled(document.uri)) return [] if (yaml.supported(document) && !yaml.ranges(document).length) return [] const range = new vscode.Range(0, 0, 0, 0) // place at top of file const lens = new vscode.CodeLens(range, { @@ -71,7 +72,12 @@ const toggleLink = { } function buildToggle (context) { - const codeLens = vscode.languages.registerCodeLensProvider([{ language: 'dotenv' }, { language: 'yaml' }], toggleLink) + const codeLens = vscode.languages.registerCodeLensProvider([{ language: 'dotenv' }, { language: 'yaml' }], { + ...toggleLink, + onDidChangeCodeLenses: listener => vscode.workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration('dotenv.enableAutocloaking')) listener() + }) + }) context.subscriptions.push(codeLens) @@ -80,6 +86,7 @@ function buildToggle (context) { async function dotenvToggleAutocloaking () { const uri = vscode.window.activeTextEditor?.document.uri + if (!settings.autocloakingFeatureEnabled(uri)) return false if (settings.autocloakingEnabled(uri)) { await settings.autocloakingOff(uri) } else { diff --git a/lib/helpers.js b/lib/helpers.js index e168a5d..5ce511e 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -46,7 +46,7 @@ function valueHover (key, document, range) { if (!settings.secretpeekingEnabled(document.uri)) return undefined const values = module.exports.envValues(document).get(key) if (!values) return new vscode.Hover(settings.missingText(), range) - const revealed = hoverReveal.begin(document, key, range, settings.secretpeekingEnabled(document.uri)) + const revealed = hoverReveal.begin(document, key, range) const contents = [valueDocumentation(values, undefined, undefined, revealed, document.uri)] const control = values.some(entry => entry.value) && hoverReveal.control(document, key, range, revealed) if (control) contents.push(control) diff --git a/lib/hover-reveal.js b/lib/hover-reveal.js index 57d6e0f..2daa627 100644 --- a/lib/hover-reveal.js +++ b/lib/hover-reveal.js @@ -11,9 +11,9 @@ function matches (request, document, key, range) { request.version === document.version && request.key === key && range && request.range.isEqual(range) } -function begin (document, key, range, defaultRevealed) { +function begin (document, key, range) { const request = pending - if (!matches(request, document, key, range)) return defaultRevealed + if (!matches(request, document, key, range)) return false pending = undefined return request.revealed } diff --git a/lib/secure-editor.js b/lib/secure-editor.js index 4c0af47..0fc8f6b 100644 --- a/lib/secure-editor.js +++ b/lib/secure-editor.js @@ -13,7 +13,7 @@ function html (webview, extensionUri) { Dotenv -
Loading…
+
Loading…
` } @@ -38,7 +38,7 @@ function resolveCustomTextEditor (document, panel, context) { const send = (extra = {}) => { if (client && !disposed) webview.postMessage({ type: 'document', client, text: text(), version: document.version, filename: path.basename(document.uri.fsPath || ''), options: options(), ...configuration(), ...extra }) } - const view = { panel, toggle: () => webview.postMessage({ type: 'toggle' }) } + const view = { panel, toggle: () => { if (configuration().autocloaking) webview.postMessage({ type: 'toggle' }) } } views.add(view) const subscriptions = [ vscode.workspace.onDidChangeTextDocument(event => { diff --git a/lib/settings.js b/lib/settings.js index abb870b..7ccf267 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -30,6 +30,7 @@ function initialize (context) { } async function setAutocloaking (enabled, uri) { + if (!autocloakingFeatureEnabled(uri)) return false const override = { enabled, configured: !!userConfig(uri).get(enableAutocloakingKey), scope: scope(uri) } overrides = overrides.filter(item => item.scope !== override.scope).concat(override) await extensionState.update(toggleStateKey, overrides) @@ -80,8 +81,13 @@ function userConfig (uri) { } // settings +function autocloakingFeatureEnabled (uri) { + return !!userConfig(uri).get(enableAutocloakingKey) +} + function autocloakingEnabled (uri) { - const configured = !!userConfig(uri).get(enableAutocloakingKey) + const configured = autocloakingFeatureEnabled(uri) + if (!configured) return false const override = overrides.find(item => item.scope === scope(uri)) return override && override.configured === configured ? override.enabled : configured } @@ -112,6 +118,7 @@ module.exports.resetAutocloaking = resetAutocloaking module.exports.removeLegacyMask = removeLegacyMask // settings +module.exports.autocloakingFeatureEnabled = autocloakingFeatureEnabled module.exports.autocloakingEnabled = autocloakingEnabled module.exports.secretpeekingEnabled = secretpeekingEnabled module.exports.cloakColor = cloakColor diff --git a/media/editor/main.js b/media/editor/main.js index 368c8d8..d49b968 100644 --- a/media/editor/main.js +++ b/media/editor/main.js @@ -114,6 +114,8 @@ function applyMask () { }) : []) status.textContent = conflict ? 'File changed elsewhere. Copy your edits before reopening.' : '' + toggle.hidden = !autocloaking + toggle.disabled = !autocloaking toggle.setAttribute('aria-label', masked ? 'Reveal dotenv values' : 'Hide dotenv values') } function conceal () { @@ -128,7 +130,7 @@ function conceal () { } function toggleMask () { encryptedHover?.hide() - if (!editor) return + if (!editor || !autocloaking) return container.classList.add('preparing') masked = !masked applyMask() @@ -210,7 +212,7 @@ function updateDocument (message) { previous = message.text applyMask() editor.render(true) - toggle.disabled = false + toggle.disabled = !autocloaking if (!document.hidden) container.classList.remove('preparing') } window.addEventListener('message', event => { diff --git a/package.json b/package.json index 7e8c813..08776ec 100644 --- a/package.json +++ b/package.json @@ -37,13 +37,13 @@ "scope": "resource", "type": "boolean", "default": true, - "description": "Enable auto-cloaking for .env files and environment values in YAML" + "description": "Enable cloaking and its toggle controls for .env files and environment values in YAML" }, "dotenv.enableSecretpeeking": { "scope": "resource", "type": "boolean", "default": true, - "description": "Enable in-code secret peeking for your environment variables" + "description": "Enable in-code secret peeking. Values start cloaked until you click Reveal value." }, "dotenv.cloakColor": { "scope": "resource", diff --git a/test/renderer/configuration.js b/test/renderer/configuration.js index dd0d1f5..8a7e0d9 100644 --- a/test/renderer/configuration.js +++ b/test/renderer/configuration.js @@ -4,13 +4,16 @@ import { sourceEditor } from '../../media/editor/main.js' window.addEventListener('message', async event => { if (event.data?.type !== 'checkCloaking') return - const { masked, id } = event.data + const { masked, id, featureEnabled = masked, clickToggle = false } = event.data try { + if (clickToggle) document.getElementById('toggle').click() for (let i = 0; i < 100; i++) { if (sourceEditor && document.getElementById('toggle').getAttribute('aria-label') === (masked ? 'Reveal dotenv values' : 'Hide dotenv values')) break await new Promise(resolve => setTimeout(resolve, 25)) } for (let i = 0; i < 3; i++) await new Promise(resolve => requestAnimationFrame(resolve)) + const toggle = document.getElementById('toggle') + if (toggle.hidden === featureEnabled || toggle.disabled === featureEnabled) throw new Error('Toggle availability must follow feature enablement') const spans = [...document.querySelectorAll('.view-line span')].filter(span => !span.children.length && span.textContent.includes('SECRET_CONFIGURATION')) if (!spans.length) throw new Error('Secret text was not rendered') for (const span of spans) { diff --git a/test/suite/extension.test.js b/test/suite/extension.test.js index acf3170..8601437 100644 --- a/test/suite/extension.test.js +++ b/test/suite/extension.test.js @@ -92,6 +92,34 @@ SINGLE='secret' } }) + it('removes native cloaking controls and ignores toggles while the feature is disabled', async function () { + this.timeout(15000) + const uri = vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, '.dev.vars') + const document = await vscode.workspace.openTextDocument(uri) + await vscode.window.showTextDocument(document) + const config = vscode.workspace.getConfiguration('dotenv', uri) + const original = config.inspect('enableAutocloaking').workspaceValue + const settings = require('../../lib/settings') + let applied + const editor = { document, setDecorations: (_, ranges) => { applied = ranges } } + try { + for (const enabled of [true, false, true]) { + await config.update('enableAutocloaking', enabled, vscode.ConfigurationTarget.Workspace) + const lenses = await vscode.commands.executeCommand('vscode.executeCodeLensProvider', uri) + assert.strictEqual(lenses.some(lens => lens.command.command === 'dotenv.toggleAutocloaking'), enabled) + if (!enabled) { + await vscode.commands.executeCommand('dotenv.toggleAutocloaking') + assert.strictEqual(settings.autocloakingEnabled(uri), false) + } + decorations.decorate({}, editor) + assert.strictEqual(applied.length, enabled ? 1 : 0) + } + } finally { + await settings.resetAutocloaking() + await config.update('enableAutocloaking', original, vscode.ConfigurationTarget.Workspace) + } + }) + it('clears the cloak and removes the toggle when switching language modes', async function () { const uri = vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, '.dev.vars') let document = await vscode.workspace.openTextDocument(uri) diff --git a/test/suite/lib/autocloaking.test.js b/test/suite/lib/autocloaking.test.js index 24fc1d2..0535e20 100644 --- a/test/suite/lib/autocloaking.test.js +++ b/test/suite/lib/autocloaking.test.js @@ -16,6 +16,7 @@ function setup () { const editor = uri => ({ document: { uri: { toString: () => uri } } }) const editors = [editor('file:///project/.env'), editor('file:///project/.env')] const settings = { + autocloakingFeatureEnabled: () => true, autocloakingEnabled: () => enabled, initialize: () => {}, removeLegacyMask: async () => {}, @@ -219,7 +220,7 @@ describe('cloaking settings isolation', () => { configure(false) assert.strictEqual(settings.autocloakingEnabled(), false) await settings.autocloakingOn() - assert.strictEqual(settings.autocloakingEnabled(), true) + assert.strictEqual(settings.autocloakingEnabled(), false) await settings.resetAutocloaking() assert.strictEqual(settings.autocloakingEnabled(), false) }) diff --git a/test/suite/lib/dotnet.test.js b/test/suite/lib/dotnet.test.js index 53df60c..0b8703c 100644 --- a/test/suite/lib/dotnet.test.js +++ b/test/suite/lib/dotnet.test.js @@ -51,7 +51,8 @@ describe('.NET hover', () => { ]) { it(`shows the .env value: ${text}`, () => { const result = dotnet.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO') + 1)) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) assert.strictEqual(result.range.start.character, text.indexOf('HELLO')) }) } @@ -59,7 +60,8 @@ describe('.NET hover', () => { it('selects the hovered call when several calls or repeated names share a line', () => { const text = 'var HELLO = Environment.GetEnvironmentVariable("UNKNOWN") + Environment.GetEnvironmentVariable("HELLO");' const result = dotnet.hover.provideHover(document(text), new vscode.Position(0, text.lastIndexOf('HELLO') + 1)) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) assert.strictEqual(dotnet.hover.provideHover(document(text), new vscode.Position(0, 5)), undefined) }) @@ -74,7 +76,7 @@ describe('.NET hover', () => { try { helpers.envValues = () => new Map(Object.entries({ lower_key: 'x', EMPTY: '' }).map(([key, value]) => [key, [{ value, source: '.env' }]])) settings.secretpeekingEnabled = () => false - for (const [key, expected] of [['lower_key', 'x'], ['EMPTY', '(empty)']]) { + for (const [key, expected] of [['lower_key', '█'], ['EMPTY', '(empty)']]) { const text = `Environment.GetEnvironmentVariable("${key}")` settings.secretpeekingEnabled = () => false assert.strictEqual(dotnet.hover.provideHover(document(text), new vscode.Position(0, text.indexOf(key))), undefined) @@ -83,7 +85,7 @@ describe('.NET hover', () => { } const text = 'Environment.GetEnvironmentVariable("HELLO")' helpers.envValues = () => new Map(Object.entries({ HELLO: 'World' }).map(([key, value]) => [key, [{ value, source: '.env' }]])) - assert(dotnet.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))).contents[0].value.includes('World')) + assert(dotnet.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))).contents[0].value.includes('█████')) helpers.envValues = () => new Map() assert.strictEqual(dotnet.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))).contents[0], settings.missingText()) } finally { @@ -104,7 +106,7 @@ describe('.NET registered providers', () => { const doc = await vscode.workspace.openTextDocument(uri) await vscode.languages.setTextDocumentLanguage(doc, language) const hover = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, new vscode.Position(0, text.indexOf('HELLO') + 1)) - assert(hover.some(item => item.contents.some(content => (content.value || content).includes('World')))) + assert(hover.some(item => item.contents.some(content => (content.value || content).includes('█████')))) const offset = text.split('\n')[1].indexOf('HE') + 2 const completions = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', uri, new vscode.Position(1, offset)) assert(completions.items.some(item => (item.label.label || item.label) === 'HELLO')) diff --git a/test/suite/lib/env-discovery-providers.test.js b/test/suite/lib/env-discovery-providers.test.js index b0d898b..f05a54f 100644 --- a/test/suite/lib/env-discovery-providers.test.js +++ b/test/suite/lib/env-discovery-providers.test.js @@ -47,8 +47,9 @@ describe('dotenv discovery through language providers', () => { assert(!item.documentation.value.includes('productionvalue')) const hovers = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, new vscode.Position(1, reference.indexOf('DISCOVERY_KEY') + 2)) const content = hovers.flatMap(hover => hover.contents).map(value => value.value || value).join('\n') - assert(content.includes('localvalue')) - assert(content.includes('productionvalue')) + assert(content.includes('█'.repeat('localvalue'.length))) + assert(!content.includes('localvalue')) + assert(!content.includes('productionvalue')) assert(content.includes('.env.local')) assert(content.includes('.env.production')) assert.strictEqual(document.getText(), `${complete}\n${reference}`) @@ -64,8 +65,9 @@ describe('dotenv discovery through language providers', () => { assert(!item.documentation.value.includes('localvalue')) const hovers = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, new vscode.Position(2, 8)) const content = hovers.flatMap(hover => hover.contents).map(value => value.value || value).join('\n') - assert(content.includes('localvalue')) - assert(content.includes('productionvalue')) + assert(content.includes('█'.repeat('localvalue'.length))) + assert(!content.includes('localvalue')) + assert(!content.includes('productionvalue')) }) } @@ -102,7 +104,7 @@ describe('dotenv discovery through language providers', () => { assert.strictEqual(helpers.valueHover('MASK_TEST', document), undefined) } settings.secretpeekingEnabled = () => true - assert(helpers.valueHover('MASK_TEST', document).contents[0].value.includes('🌴secret')) + assert(helpers.valueHover('MASK_TEST', document).contents[0].value.includes('█'.repeat('🌴secret'.length))) } finally { settings.secretpeekingEnabled = originalPeeking settings.cloakIcon = originalIcon diff --git a/test/suite/lib/env-literals.test.js b/test/suite/lib/env-literals.test.js index e1c6350..2a37712 100644 --- a/test/suite/lib/env-literals.test.js +++ b/test/suite/lib/env-literals.test.js @@ -31,7 +31,8 @@ for (const [language, provider, calls, unrelated] of [ it(`hovers the correct ${api} call and key`, () => { const text = `HELLO + ${expression(api, '"UNKNOWN"')} + ${expression(api, '"HELLO"')}` const result = provider.hover.provideHover(document(text), new vscode.Position(0, text.lastIndexOf('HELLO'))) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) assert.strictEqual(result.range.start.character, text.lastIndexOf('HELLO')) assert.strictEqual(provider.hover.provideHover(document(text), new vscode.Position(0, 1)), undefined) }) @@ -47,7 +48,7 @@ for (const [language, provider, calls, unrelated] of [ try { settings.secretpeekingEnabled = () => false helpers.envValues = () => new Map(Object.entries({ lower_key: 'World', EMPTY: '' }).map(([key, value]) => [key, [{ value, source: '.env' }]])) - for (const [key, value] of [['lower_key', 'World'], ['EMPTY', '(empty)'], ['UNKNOWN', settings.missingText()]]) { + for (const [key, value] of [['lower_key', '█████'], ['EMPTY', '(empty)'], ['UNKNOWN', settings.missingText()]]) { const text = expression(calls[0], `"${key}"`) settings.secretpeekingEnabled = () => false assert.strictEqual(provider.hover.provideHover(document(text), new vscode.Position(0, text.indexOf(key))), undefined) @@ -76,7 +77,7 @@ describe('registered environment literal providers', () => { await vscode.languages.setTextDocumentLanguage(doc, language) const position = new vscode.Position(0, expression.indexOf('HELLO') + 2) const hover = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, position) - assert(hover.some(item => item.contents.some(content => (content.value || content).includes('World')))) + assert(hover.some(item => item.contents.some(content => (content.value || content).includes('█████')))) const completions = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', uri, position) assert(completions.items.some(item => (item.label.label || item.label) === 'HELLO')) }) @@ -93,7 +94,7 @@ describe('Erlang default argument', () => { it('hovers the key without treating the default as a key', () => { const text = 'os:getenv("HELLO", "HELLO")' - assert(providers.erlang.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))).contents[0].value.includes('World')) + assert(providers.erlang.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))).contents[0].value.includes('█████')) assert.strictEqual(providers.erlang.hover.provideHover(document(text), new vscode.Position(0, text.lastIndexOf('HELLO'))), undefined) }) }) @@ -117,7 +118,8 @@ describe('Perl literal forms', () => { for (const text of ["$ENV{'HELLO'}", '$ENV{HELLO}', '$ENV{ HELLO }']) { it(`hovers ${text}`, () => { const result = providers.perl.hover.provideHover(document(text), new vscode.Position(0, text.indexOf('HELLO'))) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) assert.strictEqual(result.range.start.character, text.indexOf('HELLO')) }) } diff --git a/test/suite/lib/hover-reveal.test.js b/test/suite/lib/hover-reveal.test.js index 409725a..ac2efb9 100644 --- a/test/suite/lib/hover-reveal.test.js +++ b/test/suite/lib/hover-reveal.test.js @@ -5,7 +5,7 @@ const path = require('path') const vm = require('vm') const vscode = require('vscode') -function fixture (defaultRevealed = true) { +function fixture (featureEnabled = true) { const document = { uri: vscode.Uri.file('/test/hover.js'), version: 1 } const range = new vscode.Range(0, 12, 0, 17) const editor = { document, selection: new vscode.Selection(3, 0, 3, 0) } @@ -32,7 +32,7 @@ function fixture (defaultRevealed = true) { }) return module.exports } - const settings = { cloakIcon: () => '█', secretpeekingEnabled: () => defaultRevealed, missingText: () => 'MISSING' } + const settings = { cloakIcon: () => '█', secretpeekingEnabled: () => featureEnabled, missingText: () => 'MISSING' } const reveal = load('hover-reveal.js', { vscode: fakeVscode, './settings': settings }) const helpers = load('helpers.js', { vscode: fakeVscode, @@ -58,7 +58,7 @@ function fixture (defaultRevealed = true) { describe('hover popup reveal', () => { it('returns no hover when disabled and rejects previously issued reveal links', async () => { const f = fixture() - const masked = await f.click(f.token(f.hover())) + const masked = f.hover() const token = f.token(masked) f.settings.secretpeekingEnabled = () => false assert.strictEqual(f.hover(), undefined) @@ -68,7 +68,7 @@ describe('hover popup reveal', () => { }) it('shows and hides a masked value without changing settings or fresh hovers', async () => { const f = fixture() - const masked = await f.click(f.token(f.hover())) + const masked = f.hover() assert(masked.contents[0].value.startsWith('.env\n\n')) assert(!masked.contents[0].value.includes('SECRET')) assert(masked.contents[0].value.includes('██████')) @@ -79,21 +79,23 @@ describe('hover popup reveal', () => { assert(shown.contents[0].value.includes('SECRET')) assert(shown.contents[1].value.includes('Hide value')) assert(f.editor.selection.active.isEqual(f.range.start)) + assert(!f.hover().contents[0].value.includes('SECRET'), 'A fresh peek must recloak even after revealing another popup') const hidden = await f.click(f.token(shown)) assert(hidden.contents[0].value.startsWith('.env\n\n')) assert(!hidden.contents[0].value.includes('SECRET')) assert(hidden.contents[0].value.includes('██████')) assert.strictEqual(f.settings.secretpeekingEnabled(), true) - assert(f.hover().contents[0].value.includes('SECRET')) + assert(!f.hover().contents[0].value.includes('SECRET')) assert.strictEqual(hidden.contents[1].isTrusted.enabledCommands[0], 'dotenv.toggleHoverValue') assert.strictEqual(hidden.contents[1].isTrusted.enabledCommands.length, 1) }) it('can hide values when peeking is enabled', async () => { const f = fixture(true) - const hidden = await f.click(f.token(f.hover())) + const shown = await f.click(f.token(f.hover())) + const hidden = await f.click(f.token(shown)) assert(hidden.contents[0].value.includes('██████')) - assert(f.hover().contents[0].value.includes('SECRET')) + assert(!f.hover().contents[0].value.includes('SECRET')) assert.strictEqual(f.settings.secretpeekingEnabled(), true) }) @@ -106,7 +108,7 @@ describe('hover popup reveal', () => { const content = f.hover().contents[0].value assert(content.includes('.env\n\n')) assert(content.includes('.env.local\n\n')) - assert(content.includes('SECRET')) + assert(!content.includes('SECRET')) }) it('ignores unknown, reused, edited-document, and other-file links', async () => { @@ -157,7 +159,7 @@ it('refreshes the real VS Code hover after clicking Reveal value and Hide value' }) const token = hover => JSON.parse(decodeURIComponent(hover.contents[1].value.match(/\?([^)]*)/)[1]))[0] let current = helpers.valueHover('HELLO', document, range) - for (const expected of ['█████', 'World']) { + for (const expected of ['World', '█████']) { latest = undefined await vscode.commands.executeCommand('dotenv.toggleHoverValue', token(current)) const deadline = Date.now() + 3000 @@ -179,30 +181,43 @@ it('refreshes the real VS Code hover after clicking Reveal value and Hide value' it('disables in-code hovers when the actual secret-peeking setting is unchecked', async function () { // Settings writes and cold language-provider startup can exceed Mocha's 2s default on CI. - this.timeout(15000) + this.timeout(30000) const uri = vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0].uri, 'peeking-setting.js') const config = vscode.workspace.getConfiguration('dotenv', uri) const original = config.inspect('enableSecretpeeking').workspaceValue + const originalCloaking = config.inspect('enableAutocloaking').workspaceValue + const settings = require('../../../lib/settings') try { await vscode.extensions.getExtension('dotenv.dotenv-vscode').activate() await vscode.workspace.fs.writeFile(uri, Buffer.from('process.env.HELLO\n')) await vscode.workspace.openTextDocument(uri) - for (const enabled of [true, false, true]) { + for (const [enabled, cloaking, revealEditor] of [[true, true, false], [true, true, true], [true, false, false], [false, true, false], [false, false, false], [true, true, false]]) { + await config.update('enableAutocloaking', cloaking, vscode.ConfigurationTarget.Workspace) + await settings.resetAutocloaking() + if (revealEditor) await settings.autocloakingOff(uri) await config.update('enableSecretpeeking', enabled, vscode.ConfigurationTarget.Workspace) const hovers = await vscode.commands.executeCommand('vscode.executeHoverProvider', uri, new vscode.Position(0, 14)) const content = hovers.flatMap(hover => hover.contents).map(item => item.value || '').join('\n') assert.strictEqual(content.includes('.env'), enabled) - assert.strictEqual(content.includes('World'), enabled) + assert(!content.includes('World')) + assert.strictEqual(content.includes('█████'), enabled) const completions = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', uri, new vscode.Position(0, 12)) const item = completions.items.find(item => item.label.label === 'HELLO') assert(item, 'Autocomplete must remain available') assert.strictEqual(!!item.documentation, enabled) + if (enabled) { + assert(item.documentation.value.includes('█████')) + assert(!item.documentation.value.includes('World')) + assert(content.includes('Reveal value')) + } if (!enabled) { assert(!content.includes('█████')) assert(!content.includes('Reveal value')) } } } finally { + await settings.resetAutocloaking() + await config.update('enableAutocloaking', originalCloaking, vscode.ConfigurationTarget.Workspace) await config.update('enableSecretpeeking', original, vscode.ConfigurationTarget.Workspace) await vscode.workspace.fs.delete(uri) } diff --git a/test/suite/lib/javascript-env.test.js b/test/suite/lib/javascript-env.test.js index bd91d23..bb5ca73 100644 --- a/test/suite/lib/javascript-env.test.js +++ b/test/suite/lib/javascript-env.test.js @@ -34,7 +34,8 @@ describe('named process env imports', () => { const reference = source.split('\n')[line + 1] const start = reference.indexOf('HELLO') const hover = providers.javascriptHover.provideHover(document, new vscode.Position(line + 1, start + 2)) - assert(hover.contents[0].value.includes('World')) + assert(hover.contents[0].value.includes('█████')) + assert(!hover.contents[0].value.includes('World')) assert.strictEqual(hover.range.start.character, start) assert.strictEqual(hover.range.end.character, start + 5) const missing = providers.javascriptHover.provideHover(document, new vscode.Position(line + 1, reference.indexOf('MISSING') + 2)) diff --git a/test/suite/lib/providers.test.js b/test/suite/lib/providers.test.js index d1253b9..4dab526 100644 --- a/test/suite/lib/providers.test.js +++ b/test/suite/lib/providers.test.js @@ -440,7 +440,8 @@ describe('providers', function () { const result = providers.javascriptHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line for import.meta', async function () { @@ -460,7 +461,8 @@ describe('providers', function () { const result = providers.javascriptHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -482,7 +484,8 @@ describe('providers', function () { const result = providers.rubyHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -504,7 +507,8 @@ describe('providers', function () { const result = providers.pythonHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line for os.getenv format', async function () { @@ -524,7 +528,8 @@ describe('providers', function () { const result = providers.pythonHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line for os.environ[] format', async function () { @@ -544,7 +549,8 @@ describe('providers', function () { const result = providers.pythonHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -566,7 +572,8 @@ describe('providers', function () { const result = providers.phpHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line for $_SERVER[] format', async function () { @@ -586,7 +593,8 @@ describe('providers', function () { const result = providers.phpHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line for getenv() format', async function () { @@ -606,7 +614,8 @@ describe('providers', function () { const result = providers.phpHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -628,7 +637,8 @@ describe('providers', function () { const result = providers.goHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -650,7 +660,8 @@ describe('providers', function () { const result = providers.javaHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -672,7 +683,8 @@ describe('providers', function () { const result = providers.csharpHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -694,7 +706,8 @@ describe('providers', function () { const result = providers.rustHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) it('returns undefined at 0 line with var_os format', async function () { @@ -714,7 +727,8 @@ describe('providers', function () { const result = providers.rustHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -736,7 +750,8 @@ describe('providers', function () { const result = providers.dartHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -758,7 +773,8 @@ describe('providers', function () { const result = providers.kotlinHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) @@ -780,7 +796,8 @@ describe('providers', function () { const result = providers.elixirHover.provideHover(document, position) - assert(result.contents[0].value.includes('World')) + assert(result.contents[0].value.includes('█████')) + assert(!result.contents[0].value.includes('World')) }) }) }) diff --git a/test/suite/lib/secure-renderer.test.js b/test/suite/lib/secure-renderer.test.js index 8f782fc..aa5693d 100644 --- a/test/suite/lib/secure-renderer.test.js +++ b/test/suite/lib/secure-renderer.test.js @@ -35,15 +35,18 @@ describe('Monaco renderer integration', () => { } }) let sequence = 0 - const check = masked => new Promise((resolve, reject) => { + const check = (masked, extra = {}) => new Promise((resolve, reject) => { const id = ++sequence checks.set(id, { resolve, reject }) - panel.webview.postMessage({ type: 'checkCloaking', id, masked }) + panel.webview.postMessage({ type: 'checkCloaking', id, masked, ...extra }) }) provider.resolveCustomTextEditor(document, panel, { extensionUri: vscode.Uri.file(root) }) panel.webview.html = panel.webview.html.replace(/dist\/main.js/g, 'dist/test-renderer.js') await loaded await check(false) + await check(false, { clickToggle: true }) + await panel.webview.postMessage({ type: 'toggle' }) + await check(false) const edit = new vscode.WorkspaceEdit() edit.insert(uri, new vscode.Position(1, 0), 'NEXT=SECRET_CONFIGURATION_EDITED\n') await vscode.workspace.applyEdit(edit) @@ -56,6 +59,8 @@ describe('Monaco renderer integration', () => { await config.update('enableAutocloaking', true, vscode.ConfigurationTarget.Workspace) const star = await check(true) assert(star.colors.includes('255,0,0'), 'Initial cloak color must be used') + await check(false, { featureEnabled: true, clickToggle: true }) + await check(true, { clickToggle: true }) await config.update('cloakIcon', '?', vscode.ConfigurationTarget.Workspace) const question = await check(true) assert.notStrictEqual(question.tile, star.tile, 'Changing the icon must change the rendered mask')