diff --git a/.changeset/afraid-years-rule.md b/.changeset/afraid-years-rule.md new file mode 100644 index 000000000..eeba9060a --- /dev/null +++ b/.changeset/afraid-years-rule.md @@ -0,0 +1,5 @@ +--- +"@shopify/theme-language-server-common": patch +--- + +Read only liquid and JSON files from theme directories (assets, blocks, config, layout, locales, sections, snippets, templates) when preloading files diff --git a/packages/theme-language-server-common/src/documents/DocumentManager.spec.ts b/packages/theme-language-server-common/src/documents/DocumentManager.spec.ts index 59a663ca7..3ff9b5f65 100644 --- a/packages/theme-language-server-common/src/documents/DocumentManager.spec.ts +++ b/packages/theme-language-server-common/src/documents/DocumentManager.spec.ts @@ -43,8 +43,8 @@ describe('Module: DocumentManager', () => { beforeEach(async () => { fs = new MockFileSystem( { - 'snippet/foo.liquid': `hello {% render 'bar' %}`, - 'snippet/bar.liquid': `world`, + 'snippets/foo.liquid': `hello {% render 'bar' %}`, + 'snippets/bar.liquid': `world`, }, 'mock-fs:', ); @@ -58,14 +58,14 @@ describe('Module: DocumentManager', () => { }); it('preloads source codes with a version of undefined', async () => { - const sc = documentManager.get('mock-fs:/snippet/foo.liquid'); + const sc = documentManager.get('mock-fs:/snippets/foo.liquid'); assert(sc); expect(sc.version).to.equal(undefined); }); it('returns defined versions of opened files', () => { - documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 0); - const sc = documentManager.get('mock-fs:/snippet/foo.liquid'); + documentManager.open('mock-fs:/snippets/foo.liquid', 'hello {% render "bar" %}', 0); + const sc = documentManager.get('mock-fs:/snippets/foo.liquid'); assert(sc); expect(sc.version).to.equal(0); }); @@ -84,9 +84,9 @@ describe('Module: DocumentManager', () => { describe('Unit: close(uri)', () => { it('sets the source version to undefined (value is on disk)', () => { - documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 10); - documentManager.close('mock-fs:/snippet/foo.liquid'); - const sc = documentManager.get('mock-fs:/snippet/foo.liquid'); + documentManager.open('mock-fs:/snippets/foo.liquid', 'hello {% render "bar" %}', 10); + documentManager.close('mock-fs:/snippets/foo.liquid'); + const sc = documentManager.get('mock-fs:/snippets/foo.liquid'); assert(sc); expect(sc.source).to.equal('hello {% render "bar" %}'); expect(sc.version).to.equal(undefined); @@ -96,9 +96,9 @@ describe('Module: DocumentManager', () => { describe('Unit: delete(uri)', () => { it('deletes the source code from the document manager', () => { // as though the file no longer exists - documentManager.open('mock-fs:/snippet/foo.liquid', 'hello {% render "bar" %}', 10); - documentManager.delete('mock-fs:/snippet/foo.liquid'); - const sc = documentManager.get('mock-fs:/snippet/foo.liquid'); + documentManager.open('mock-fs:/snippets/foo.liquid', 'hello {% render "bar" %}', 10); + documentManager.delete('mock-fs:/snippets/foo.liquid'); + const sc = documentManager.get('mock-fs:/snippets/foo.liquid'); assert(!sc); }); }); @@ -137,16 +137,16 @@ describe('Module: DocumentManager', () => { fs = new MockFileSystem( { - 'snippet/1.liquid': `hello {% render 'bar' %}`, - 'snippet/2.liquid': `hello {% render 'bar' %}`, - 'snippet/3.liquid': `hello {% render 'bar' %}`, - 'snippet/4.liquid': `hello {% render 'bar' %}`, - 'snippet/5.liquid': `hello {% render 'bar' %}`, - 'snippet/6.liquid': `hello {% render 'bar' %}`, - 'snippet/7.liquid': `hello {% render 'bar' %}`, - 'snippet/8.liquid': `hello {% render 'bar' %}`, - 'snippet/9.liquid': `hello {% render 'bar' %}`, - 'snippet/10.liquid': `hello {% render 'bar' %}`, + 'snippets/1.liquid': `hello {% render 'bar' %}`, + 'snippets/2.liquid': `hello {% render 'bar' %}`, + 'snippets/3.liquid': `hello {% render 'bar' %}`, + 'snippets/4.liquid': `hello {% render 'bar' %}`, + 'snippets/5.liquid': `hello {% render 'bar' %}`, + 'snippets/6.liquid': `hello {% render 'bar' %}`, + 'snippets/7.liquid': `hello {% render 'bar' %}`, + 'snippets/8.liquid': `hello {% render 'bar' %}`, + 'snippets/9.liquid': `hello {% render 'bar' %}`, + 'snippets/10.liquid': `hello {% render 'bar' %}`, }, mockRoot, ); diff --git a/packages/theme-language-server-common/src/documents/DocumentManager.ts b/packages/theme-language-server-common/src/documents/DocumentManager.ts index 6102b6e25..c3dd45759 100644 --- a/packages/theme-language-server-common/src/documents/DocumentManager.ts +++ b/packages/theme-language-server-common/src/documents/DocumentManager.ts @@ -3,7 +3,6 @@ import { assertNever, memoize, path, - recursiveReadDirectory, SourceCodeType, Theme, toSourceCode, @@ -13,6 +12,7 @@ import { memo, Mode, isError, + recursiveReadDirectory, } from '@shopify/theme-check-common'; import { Connection } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -131,13 +131,28 @@ export class DocumentManager { progress.start('Initializing Liquid LSP'); - // We'll only load the files that aren't already in the store. No need to - // parse a file we already parsed. - const filesToLoad = await recursiveReadDirectory( - this.fs, - rootUri, - ([uri]) => /\.(liquid|json)$/.test(uri) && !this.sourceCodes.has(uri), - ); + // NOTE: Only read known theme dirs, a stray 100MB JSON file can consume 1-2GB of heap. + // See https://shopify.dev/docs/storefronts/themes/architecture#directory-structure-and-component-types + const filesToLoad = ( + await Promise.all( + [ + 'assets', + 'blocks', + 'config', + 'layout', + 'locales', + 'sections', + 'snippets', + 'templates', + ].map((dir) => + recursiveReadDirectory( + fs, + path.join(rootUri, dir), + ([uri]) => /\.(liquid|json)$/.test(uri) && !this.sourceCodes.has(uri), + ).catch(() => []), + ), + ) + ).flat(); progress.report(10, 'Preloading files');