Skip to content

Commit

Permalink
Add block rename handler (replace old->new everywhere!) (#655)
Browse files Browse the repository at this point in the history
* Add `offset` property on Schema objects

Saves you some work to get position info

* Move isLiquidSourceCode helper for reuse

* Add BlockRenameHandler

Co-authored-by: Navdeep Singh <[email protected]>

Whenever a block gets renamed, the following will now happen:
  1. References in files with a {% schema %} will be changed
  2. References in template files will be changed
  3. References in section groups will be changed
  4. References in {% content_for "block", type: "oldName" %} will be changed

* Add changesets

* Remove bugged support for subfolder renames

* Clean up function that can be hoisted
  • Loading branch information
charlespwd authored Jan 9, 2025
1 parent dc9c6da commit b31e0f8
Show file tree
Hide file tree
Showing 12 changed files with 1,008 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .changeset/green-mice-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/theme-check-common': minor
---

[internal] Add `offset` information on Schema objects
12 changes: 12 additions & 0 deletions .changeset/wild-beers-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@shopify/theme-language-server-common': minor
'theme-check-vscode': minor
---

Add "On theme block rename" handling

Whenever a theme block gets renamed, the following will now happen:
1. References in files with a `{% schema %}` will be updated automatically
2. References in template files will be updated automatically
3. References in section groups will be updated automatically
4. References in `{% content_for "block", type: "oldName" %}` will be updated automatically
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('Module: ValidSchemaName', () => {
validSchema: new Error('Invalid schema'),
name: 'file',
type: ThemeSchemaType.Section,
offset: 0,
}),
});

Expand Down
7 changes: 5 additions & 2 deletions packages/theme-check-common/src/to-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ export function isSection(uri: UriString) {
}

export function isBlockSchema(
schema: SectionSchema | ThemeBlockSchema | undefined,
schema: AppBlockSchema | SectionSchema | ThemeBlockSchema | undefined,
): schema is ThemeBlockSchema {
return schema?.type === ThemeSchemaType.Block;
}

export function isSectionSchema(
schema: SectionSchema | ThemeBlockSchema | undefined,
schema: AppBlockSchema | SectionSchema | ThemeBlockSchema | undefined,
): schema is SectionSchema {
return schema?.type === ThemeSchemaType.Section;
}
Expand Down Expand Up @@ -84,6 +84,7 @@ export async function toBlockSchema(
return {
type: ThemeSchemaType.Block,
validSchema: await toValidSchema<ThemeBlock.Schema>(uri, schemaNode, parsed, isValidSchema),
offset: schemaNode instanceof Error ? 0 : schemaNode.blockStartPosition.end,
name,
parsed,
ast,
Expand All @@ -105,6 +106,7 @@ export async function toSectionSchema(
return {
type: ThemeSchemaType.Section,
validSchema: await toValidSchema(uri, schemaNode, parsed, isValidSchema),
offset: schemaNode instanceof Error ? 0 : schemaNode.blockStartPosition.end,
name,
parsed,
ast,
Expand All @@ -122,6 +124,7 @@ export async function toAppBlockSchema(
const ast = toAst(schemaNode);
return {
type: ThemeSchemaType.AppBlock,
offset: schemaNode instanceof Error ? 0 : schemaNode.blockStartPosition.end,
name,
parsed,
ast,
Expand Down
3 changes: 3 additions & 0 deletions packages/theme-check-common/src/types/theme-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export interface ThemeSchema<T extends ThemeSchemaType> {

/** Parsed as a JavaScript object or an Error */
parsed: any | Error;

/** 0-based index of the start of JSON object in the document */
offset: number;
}

/** See {@link ThemeSchema} */
Expand Down
6 changes: 6 additions & 0 deletions packages/theme-language-server-common/src/documents/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ export type AugmentedSourceCode<SCT extends SourceCodeType = SourceCodeType> = {
[SourceCodeType.JSON]: AugmentedJsonSourceCode;
[SourceCodeType.LiquidHtml]: AugmentedLiquidSourceCode;
}[SCT];

export const isLiquidSourceCode = (file: AugmentedSourceCode): file is AugmentedLiquidSourceCode =>
file.type === SourceCodeType.LiquidHtml;

export const isJsonSourceCode = (file: AugmentedSourceCode): file is AugmentedJsonSourceCode =>
file.type === SourceCodeType.JSON;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { path } from '@shopify/theme-check-common';
import { Connection } from 'vscode-languageserver';
import { RenameFilesParams } from 'vscode-languageserver-protocol';
import { ClientCapabilities } from '../ClientCapabilities';
import { DocumentManager } from '../documents';
import { BaseRenameHandler } from './BaseRenameHandler';
import { AssetRenameHandler } from './handlers/AssetRenameHandler';
import { BlockRenameHandler } from './handlers/BlockRenameHandler';
import { SnippetRenameHandler } from './handlers/SnippetRenameHandler';

/**
Expand All @@ -26,6 +26,7 @@ export class RenameHandler {
this.handlers = [
new SnippetRenameHandler(documentManager, connection, capabilities, findThemeRootURI),
new AssetRenameHandler(documentManager, connection, capabilities, findThemeRootURI),
new BlockRenameHandler(documentManager, connection, capabilities, findThemeRootURI),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
WorkspaceEdit,
} from 'vscode-languageserver-protocol';
import { ClientCapabilities } from '../../ClientCapabilities';
import { AugmentedLiquidSourceCode, AugmentedSourceCode, DocumentManager } from '../../documents';
import { DocumentManager, isLiquidSourceCode } from '../../documents';
import { assetName, isAsset } from '../../utils/uri';
import { BaseRenameHandler } from '../BaseRenameHandler';

Expand All @@ -35,77 +35,72 @@ export class AssetRenameHandler implements BaseRenameHandler {

async onDidRenameFiles(params: RenameFilesParams): Promise<void> {
if (!this.capabilities.hasApplyEditSupport) return;
const isLiquidSourceCode = (file: AugmentedSourceCode): file is AugmentedLiquidSourceCode =>
file.type === SourceCodeType.LiquidHtml;

const relevantRenames = params.files.filter(
(file) => isAsset(file.oldUri) && isAsset(file.newUri),
);

// Only preload if you have something to do
if (relevantRenames.length === 0) return;
// Only preload if you have something to do (folder renames are not supported)
if (relevantRenames.length !== 1) return;
const rename = relevantRenames[0];
const rootUri = await this.findThemeRootURI(path.dirname(params.files[0].oldUri));
await this.documentManager.preload(rootUri);
const theme = this.documentManager.theme(rootUri, true);
const liquidSourceCodes = theme.filter(isLiquidSourceCode);

const promises = relevantRenames.map(async (file) => {
const oldAssetName = assetName(file.oldUri);
const newAssetName = assetName(file.newUri);
const editLabel = `Rename asset '${oldAssetName}' to '${newAssetName}'`;
const annotationId = 'renameAsset';
const workspaceEdit: WorkspaceEdit = {
documentChanges: [],
changeAnnotations: {
[annotationId]: {
label: editLabel,
needsConfirmation: false,
},
const oldAssetName = assetName(rename.oldUri);
const newAssetName = assetName(rename.newUri);
const editLabel = `Rename asset '${oldAssetName}' to '${newAssetName}'`;
const annotationId = 'renameAsset';
const workspaceEdit: WorkspaceEdit = {
documentChanges: [],
changeAnnotations: {
[annotationId]: {
label: editLabel,
needsConfirmation: false,
},
};
},
};

for (const sourceCode of liquidSourceCodes) {
if (sourceCode.ast instanceof Error) continue;
const textDocument = sourceCode.textDocument;
const edits: TextEdit[] = visit<SourceCodeType.LiquidHtml, TextEdit>(sourceCode.ast, {
LiquidVariable(node: LiquidVariable) {
if (node.filters.length === 0) return;
if (node.expression.type !== NodeTypes.String) return;
if (node.filters[0].name !== 'asset_url') return;
const assetName = node.expression.value;
if (assetName !== oldAssetName) return;
return {
newText: newAssetName,
range: Range.create(
textDocument.positionAt(node.expression.position.start + 1), // +1 to skip the opening quote
textDocument.positionAt(node.expression.position.end - 1), // -1 to skip the closing quote
),
};
},
});
for (const sourceCode of liquidSourceCodes) {
if (sourceCode.ast instanceof Error) continue;
const textDocument = sourceCode.textDocument;
const edits: TextEdit[] = visit<SourceCodeType.LiquidHtml, TextEdit>(sourceCode.ast, {
LiquidVariable(node: LiquidVariable) {
if (node.filters.length === 0) return;
if (node.expression.type !== NodeTypes.String) return;
if (node.filters[0].name !== 'asset_url') return;
const assetName = node.expression.value;
if (assetName !== oldAssetName) return;
return {
newText: newAssetName,
range: Range.create(
textDocument.positionAt(node.expression.position.start + 1), // +1 to skip the opening quote
textDocument.positionAt(node.expression.position.end - 1), // -1 to skip the closing quote
),
};
},
});

if (edits.length === 0) continue;
workspaceEdit.documentChanges!.push({
textDocument: {
uri: textDocument.uri,
version: sourceCode.version ?? null /* null means file from disk in this API */,
},
annotationId,
edits,
});
}
if (edits.length === 0) continue;
workspaceEdit.documentChanges!.push({
textDocument: {
uri: textDocument.uri,
version: sourceCode.version ?? null /* null means file from disk in this API */,
},
annotationId,
edits,
});
}

if (workspaceEdit.documentChanges!.length === 0) {
console.error('Nothing to do!');
return;
}
if (workspaceEdit.documentChanges!.length === 0) {
console.error('Nothing to do!');
return;
}

return this.connection.sendRequest(ApplyWorkspaceEditRequest.type, {
label: editLabel,
edit: workspaceEdit,
});
await this.connection.sendRequest(ApplyWorkspaceEditRequest.type, {
label: editLabel,
edit: workspaceEdit,
});

await Promise.all(promises);
}
}
Loading

0 comments on commit b31e0f8

Please sign in to comment.