-
Notifications
You must be signed in to change notification settings - Fork 84
feat: scope Liquid objects to step URIs for Flow config field LSP support #1238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e83e5af
8cbc887
f6b4555
624b320
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -103,6 +103,25 @@ export class AugmentedThemeDocset implements ThemeDocset { | |
|
|
||
| public isAugmented = true; | ||
|
|
||
| private objectsByPrefix = new Map<string, ObjectEntry[]>(); | ||
|
|
||
| setObjectsForURI(uri: string, objects: ObjectEntry[]) { | ||
| this.objectsByPrefix.set(uri, objects); | ||
| } | ||
|
|
||
| getObjectsForURI(uri: string): ObjectEntry[] | undefined { | ||
| // Exact match first | ||
| const exact = this.objectsByPrefix.get(uri); | ||
| if (exact) return exact; | ||
|
|
||
| // Prefix match: allows registering by step path and matching file URIs within it | ||
| for (const [prefix, objects] of this.objectsByPrefix) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A URI under step-10 can match a previously registered step-1 prefix, and overlapping prefixes will not pick the most specific match. This should use a normalized prefix boundary and/or longest-prefix match. |
||
| if (uri.startsWith(prefix)) return objects; | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| filters = memo(async (): Promise<FilterEntry[]> => { | ||
| const officialFilters = await this.themeDocset.filters(); | ||
| return [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,7 @@ export class TypeSystem { | |
| private readonly getThemeSettingsSchemaForURI: GetThemeSettingsSchemaForURI, | ||
| private readonly getMetafieldDefinitions: (rootUri: string) => Promise<MetafieldDefinitionMap>, | ||
| private readonly getModeForURI: GetModeForURI = async () => 'theme', | ||
| private readonly isUriScopedMode: () => boolean = () => false, | ||
| ) {} | ||
|
|
||
| async inferType( | ||
|
|
@@ -123,7 +124,7 @@ export class TypeSystem { | |
| */ | ||
| public objectMap = async (uri: string, ast: LiquidHtmlNode): Promise<ObjectMap> => { | ||
| const [objectMap, themeSettingProperties, metafieldDefinitionsObjectMap] = await Promise.all([ | ||
| this._objectMap(), | ||
| this._objectMap(uri), | ||
| this.themeSettingProperties(uri), | ||
| this.metafieldDefinitionsObjectMap(uri), | ||
| ]); | ||
|
|
@@ -261,9 +262,19 @@ export class TypeSystem { | |
| return result; | ||
| } | ||
|
|
||
| // This is the big one we reuse (memoized) | ||
| private _objectMap = memo(async (): Promise<ObjectMap> => { | ||
| const entries = await this.objectEntries(); | ||
| private async _objectMap(uri?: string): Promise<ObjectMap> { | ||
| if (!this.isUriScopedMode()) return this._memoObjectMap(); | ||
| const entries = await this.objectEntries(uri); | ||
| return entries.reduce((map, entry) => { | ||
| map[entry.name] = entry; | ||
| return map; | ||
| }, {} as ObjectMap); | ||
| } | ||
|
|
||
| private _memoObjectEntries = memo(() => this.themeDocset.objects()); | ||
|
|
||
| private _memoObjectMap = memo(async (): Promise<ObjectMap> => { | ||
| const entries = await this._memoObjectEntries(); | ||
| return entries.reduce((map, entry) => { | ||
| map[entry.name] = entry; | ||
| return map; | ||
|
|
@@ -283,9 +294,17 @@ export class TypeSystem { | |
| return this.themeDocset.filters(); | ||
| }); | ||
|
|
||
| public objectEntries = memo(async () => { | ||
| return this.themeDocset.objects(); | ||
| }); | ||
| public hasObjectsForURI(uri: string): boolean { | ||
| return !!this.themeDocset.getObjectsForURI?.(uri); | ||
| } | ||
|
|
||
| public async objectEntries(uri?: string): Promise<ObjectEntry[]> { | ||
| if (this.isUriScopedMode() && uri && this.themeDocset.getObjectsForURI) { | ||
| const perURI = this.themeDocset.getObjectsForURI(uri); | ||
| if (perURI) return perURI; | ||
| } | ||
|
Comment on lines
+302
to
+305
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we okay if Flow sends only step-specific objects, built-in Shopify types and augmented objects disappear for completion/hover/diagnostics? More question than concern here. |
||
| return this._memoObjectEntries(); | ||
| } | ||
|
|
||
| private async symbolsTable(partialAst: LiquidHtmlNode, uri: string): Promise<SymbolsTable> { | ||
| const seedSymbolsTable = await this.seedSymbolsTable(uri); | ||
|
|
@@ -303,7 +322,7 @@ export class TypeSystem { | |
| */ | ||
| private seedSymbolsTable = async (uri: string) => { | ||
| const [globalVariables, contextualVariables] = await Promise.all([ | ||
| this.globalVariables(), | ||
| this.globalVariables(uri), | ||
| this.contextualVariables(uri), | ||
| ]); | ||
| return globalVariables.concat(contextualVariables).reduce((table, objectEntry) => { | ||
|
|
@@ -317,15 +336,23 @@ export class TypeSystem { | |
| }, {} as SymbolsTable); | ||
| }; | ||
|
|
||
| private globalVariables = memo(async () => { | ||
| const entries = await this.objectEntries(); | ||
| private _memoGlobalVariables = memo(async () => { | ||
| const entries = await this._memoObjectEntries(); | ||
| return entries.filter( | ||
| (entry) => !entry.access || entry.access.global === true || entry.access.template.length > 0, | ||
| ); | ||
| }); | ||
|
|
||
| private globalVariables = async (uri?: string) => { | ||
| if (!this.isUriScopedMode()) return this._memoGlobalVariables(); | ||
| const entries = await this.objectEntries(uri); | ||
| return entries.filter( | ||
| (entry) => !entry.access || entry.access.global === true || entry.access.template.length > 0, | ||
| ); | ||
| }; | ||
|
|
||
| private contextualVariables = async (uri: string) => { | ||
| const [entries, mode] = await Promise.all([this.objectEntries(), this.getModeForURI(uri)]); | ||
| const [entries, mode] = await Promise.all([this.objectEntries(uri), this.getModeForURI(uri)]); | ||
| const contextualEntries = getContextualEntries(uri, mode); | ||
| return entries.filter((entry) => contextualEntries.includes(entry.name)); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ import { RenameHandler } from '../renamed/RenameHandler'; | |
| import { GetTranslationsForURI } from '../translations'; | ||
| import { | ||
| Dependencies, | ||
| SetObjectsNotification, | ||
| ThemeGraphDeadCodeRequest, | ||
| ThemeGraphDependenciesRequest, | ||
| ThemeGraphReferenceRequest, | ||
|
|
@@ -341,6 +342,7 @@ export function startServer( | |
| getMetafieldDefinitions, | ||
| getDocDefinitionForURI, | ||
| getModeForURI, | ||
| isUriScopedMode: () => clientCapabilities.supportsSetObjects, | ||
| }); | ||
| const hoverProvider = new HoverProvider( | ||
| documentManager, | ||
|
|
@@ -350,6 +352,7 @@ export function startServer( | |
| getThemeSettingsSchemaForURI, | ||
| getDocDefinitionForURI, | ||
| getModeForURI, | ||
| () => clientCapabilities.supportsSetObjects, | ||
| ); | ||
|
|
||
| const executeCommandProvider = new ExecuteCommandProvider( | ||
|
|
@@ -762,5 +765,9 @@ export function startServer( | |
| return deadFiles; | ||
| }); | ||
|
|
||
| connection.onNotification(SetObjectsNotification.type, ({ uri, objects }) => { | ||
| themeDocset.setObjectsForURI(uri, objects); | ||
| }); | ||
|
Comment on lines
+768
to
+770
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why a notification and not a request? Does the client not care about if/when this resolves? Don't care to await to make sure the objects are set before proceeding?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100% valid concern e.g.
|
||
|
|
||
| connection.listen(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to also update theme-language-server-common's package.json so it uses it