From 678f771bf668833ca7dd24148ddacdc4b48e0239 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Thu, 3 Jul 2025 14:10:17 +0100 Subject: [PATCH 1/3] test that autocomplete works without the fallback, re-order the tests --- .../src/autocompleter.spec.ts | 74 +++++++++++-------- packages/ts-autocomplete/src/index.ts | 23 +++--- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts b/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts index ea1f6417..b2d16294 100644 --- a/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts +++ b/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts @@ -3,6 +3,7 @@ import { MongoDBAutocompleter } from './index'; import type { AutocompletionContext } from './autocompletion-context'; import { analyzeDocuments } from 'mongodb-schema'; import { expect } from 'chai'; +import { relativeNodePath } from '@mongodb-js/ts-autocomplete'; /* This is intended as deliberately diabolical database and collection names to @@ -25,6 +26,7 @@ describe('MongoDBAutocompleter', function () { let fallbackServiceHost: ts.LanguageServiceHost; let autocompleterContext: AutocompletionContext; let autocompleter: MongoDBAutocompleter; + let autocompleterWithFallback: MongoDBAutocompleter; let encounteredPaths: EncounteredPaths; beforeEach(function () { @@ -51,19 +53,19 @@ describe('MongoDBAutocompleter', function () { ts.sys.readFile(fileName) || '', ); - encounteredPaths.getScriptSnapshot.push(fileName); + encounteredPaths.getScriptSnapshot.push(relativeNodePath(fileName)); return result; }, fileExists: (fileName: string) => { const result = ts.sys.fileExists(fileName); if (result) { - encounteredPaths.fileExists.push(fileName); + encounteredPaths.fileExists.push(relativeNodePath(fileName)); } return result; }, readFile: (fileName: string) => { const result = ts.sys.readFile(fileName); - encounteredPaths.readFile.push(fileName); + encounteredPaths.readFile.push(relativeNodePath(fileName)); return result; }, readDirectory: (...args) => { @@ -126,11 +128,21 @@ describe('MongoDBAutocompleter', function () { autocompleter = new MongoDBAutocompleter({ context: autocompleterContext, + }); + + autocompleterWithFallback = new MongoDBAutocompleter({ + context: autocompleterContext, fallbackServiceHost, }); }); - afterEach(function () { + it('autocompletes', async function () { + await autocompleterWithFallback.autocomplete('db.foo.find({ fo'); + + encounteredPaths.fileExists.sort(); + encounteredPaths.getScriptSnapshot.sort(); + encounteredPaths.readFile.sort(); + // this is what tells us what we're missing in extract-types.ts expect(encounteredPaths).to.deep.equal({ fileExists: [], @@ -139,33 +151,6 @@ describe('MongoDBAutocompleter', function () { }); }); - it('deals with no connection', async function () { - // The body of tests are all wrapped in loops so that we exercise the - // caching logic in the autocompleter. - for (let i = 0; i < 2; i++) { - autocompleterContext.currentDatabaseAndConnection = () => { - return undefined; - }; - - const completions = await autocompleter.autocomplete('db.'); - expect(completions).to.deep.equal([]); - } - }); - - it('does not leak the bson package', async function () { - for (let i = 0; i < 2; i++) { - const completions = await autocompleter.autocomplete('bson.'); - expect(completions).to.deep.equal([]); - } - }); - - it('does not leak the ShellAPI package', async function () { - for (let i = 0; i < 2; i++) { - const completions = await autocompleter.autocomplete('ShellAPI.'); - expect(completions).to.deep.equal([]); - } - }); - it('completes a bson expression', async function () { for (let i = 0; i < 2; i++) { const completions = await autocompleter.autocomplete('Ob'); @@ -285,6 +270,33 @@ describe('MongoDBAutocompleter', function () { ]); }); + it('deals with no connection', async function () { + // The body of tests are all wrapped in loops so that we exercise the + // caching logic in the autocompleter. + for (let i = 0; i < 2; i++) { + autocompleterContext.currentDatabaseAndConnection = () => { + return undefined; + }; + + const completions = await autocompleter.autocomplete('db.'); + expect(completions).to.deep.equal([]); + } + }); + + it('does not leak the bson package', async function () { + for (let i = 0; i < 2; i++) { + const completions = await autocompleter.autocomplete('bson.'); + expect(completions).to.deep.equal([]); + } + }); + + it('does not leak the ShellAPI package', async function () { + for (let i = 0; i < 2; i++) { + const completions = await autocompleter.autocomplete('ShellAPI.'); + expect(completions).to.deep.equal([]); + } + }); + describe('getConnectionSchemaCode', function () { it('generates code for a connection', async function () { const docs = [ diff --git a/packages/ts-autocomplete/src/index.ts b/packages/ts-autocomplete/src/index.ts index f0274036..630de3a2 100644 --- a/packages/ts-autocomplete/src/index.ts +++ b/packages/ts-autocomplete/src/index.ts @@ -13,7 +13,7 @@ type UpdateDefinitionFunction = ( newDef: Record, ) => void; -function relativeNodePath(fileName: string): string { +export function relativeNodePath(fileName: string): string { const parts = fileName.split(/\/node_modules\//g); if (parts.length === 1 && fileName.endsWith('package.json')) { // special case: when it looks up this package itself it isn't going to find @@ -65,13 +65,14 @@ function getVirtualLanguageService( return (versions[fileName] ?? 1).toString(); }, getScriptSnapshot: (fileName) => { - fileName = relativeNodePath(fileName); - if (fileName in codeHolder) { + const relativeFileName = relativeNodePath(fileName); + + if (relativeFileName in codeHolder) { // if its a boolean rather than code, just return a blank string if for // some reason we ever get here. const code = - typeof codeHolder[fileName] === 'string' - ? (codeHolder[fileName] as string) + typeof codeHolder[relativeFileName] === 'string' + ? (codeHolder[relativeFileName] as string) : ''; return ts.ScriptSnapshot.fromString(code); } @@ -86,8 +87,8 @@ function getVirtualLanguageService( return ts.getDefaultLibFilePath(options); }, fileExists: (fileName) => { - fileName = relativeNodePath(fileName); - if (fileName in codeHolder) { + const relativeFileName = relativeNodePath(fileName); + if (relativeFileName in codeHolder) { return true; } @@ -98,13 +99,13 @@ function getVirtualLanguageService( return false; }, readFile: (fileName) => { - fileName = relativeNodePath(fileName); - if (fileName in codeHolder) { + const relativeFileName = relativeNodePath(fileName); + if (relativeFileName in codeHolder) { // if its a boolean rather than code, just return a blank string if for // some reason we ever get here. const code = - typeof codeHolder[fileName] === 'string' - ? (codeHolder[fileName] as string) + typeof codeHolder[relativeFileName] === 'string' + ? (codeHolder[relativeFileName] as string) : undefined; return code; } From 46fce5795112a6f20db6712069fefd7088e6fa78 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 4 Jul 2025 14:53:31 +0100 Subject: [PATCH 2/3] comment --- .../mongodb-ts-autocomplete/src/autocompleter.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts b/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts index b2d16294..595e30c9 100644 --- a/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts +++ b/packages/mongodb-ts-autocomplete/src/autocompleter.spec.ts @@ -136,6 +136,17 @@ describe('MongoDBAutocompleter', function () { }); }); + /* +This test can be used to recreate the list of deps in extract-types.ts. + +ie. if you comment out the deps structure so it is an empty object, run +extract-types (so it is just everything except the node types and Javascript +lib) and then run this test, then it will essentially print what that structure +needs to be. + +The other tests would fail at the same time because they don't use the fallback +service host, so typescript wouldn't load all the dependencies. + */ it('autocompletes', async function () { await autocompleterWithFallback.autocomplete('db.foo.find({ fo'); From f6c2d014383e509cead0b3d29b927074b3480268 Mon Sep 17 00:00:00 2001 From: Le Roux Bodenstein Date: Fri, 4 Jul 2025 15:09:11 +0100 Subject: [PATCH 3/3] add events.js --- packages/mongodb-ts-autocomplete/scripts/extract-types.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/mongodb-ts-autocomplete/scripts/extract-types.ts b/packages/mongodb-ts-autocomplete/scripts/extract-types.ts index 0c98f9a7..9b13d3a6 100644 --- a/packages/mongodb-ts-autocomplete/scripts/extract-types.ts +++ b/packages/mongodb-ts-autocomplete/scripts/extract-types.ts @@ -141,7 +141,10 @@ const deps: Record = { 'assert.js', // exists only ], buffer: ['package.json', 'index.d.ts'], - events: ['package.json'], + events: [ + 'package.json', + 'events.js', // exists only (also only on windows) + ], punycode: [ 'package.json', 'punycode.js', // exists only