From ee26e9e10e17ac37daed2549e27b958d129a51ad Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 09:56:02 +0800
Subject: [PATCH 1/7] feat(theme-language-server-common): add disable-check
'this line' code action
---
.../src/codeActions/CodeActionsProvider.ts | 15 ++-
.../providers/DisableCheckProvider.spec.ts | 93 +++++++++++++++++++
.../providers/DisableCheckProvider.ts | 58 ++++++++++++
.../src/codeActions/providers/index.ts | 1 +
.../src/codeActions/providers/utils.ts | 24 ++++-
5 files changed, 188 insertions(+), 3 deletions(-)
create mode 100644 packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
create mode 100644 packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
diff --git a/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts b/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
index fe05cf119..ab1940668 100644
--- a/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
@@ -1,11 +1,21 @@
import { CodeAction, CodeActionParams, Command } from 'vscode-languageserver';
import { DiagnosticsManager } from '../diagnostics';
import { DocumentManager } from '../documents';
-import { FixAllProvider, FixProvider, SuggestionProvider } from './providers';
+import {
+ DisableCheckProvider,
+ FixAllProvider,
+ FixProvider,
+ SuggestionProvider,
+} from './providers';
import { BaseCodeActionsProvider } from './BaseCodeActionsProvider';
export const CodeActionKinds = Array.from(
- new Set([FixAllProvider.kind, FixProvider.kind, SuggestionProvider.kind]),
+ new Set([
+ FixAllProvider.kind,
+ FixProvider.kind,
+ SuggestionProvider.kind,
+ DisableCheckProvider.kind,
+ ]),
);
export class CodeActionsProvider {
@@ -16,6 +26,7 @@ export class CodeActionsProvider {
new FixAllProvider(documentManager, diagnosticsManager),
new FixProvider(documentManager, diagnosticsManager),
new SuggestionProvider(documentManager, diagnosticsManager),
+ new DisableCheckProvider(documentManager, diagnosticsManager),
];
}
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
new file mode 100644
index 000000000..cb9142d74
--- /dev/null
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -0,0 +1,93 @@
+import { Offense, Severity, SourceCodeType, path } from '@shopify/theme-check-common';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { TextDocument } from 'vscode-languageserver-textdocument';
+import { URI } from 'vscode-uri';
+import { DiagnosticsManager } from '../../diagnostics';
+import { DocumentManager } from '../../documents';
+import { DisableCheckProvider } from './DisableCheckProvider';
+
+describe('Unit: DisableCheckProvider', () => {
+ const uri = path.normalize(URI.file('/path/to/file.liquid'));
+ const contents = `
+ {% assign x = 1 %}
+
+
+ `;
+ const version = 0;
+ const document = TextDocument.create(uri, 'liquid', version, contents);
+ let documentManager: DocumentManager;
+ let diagnosticsManager: DiagnosticsManager;
+ let provider: DisableCheckProvider;
+
+ function makeOffense(
+ checkName: string,
+ needle: string,
+ ): Offense {
+ const start = contents.indexOf(needle);
+ const end = start + needle.length;
+ return {
+ type: SourceCodeType.LiquidHtml,
+ check: checkName,
+ message: `${checkName} problem`,
+ uri: 'file:///path/to/file.liquid',
+ severity: Severity.ERROR,
+ start: { ...document.positionAt(start), index: start },
+ end: { ...document.positionAt(end), index: end },
+ } as Offense;
+ }
+
+ function cursorAt(needle: string) {
+ return {
+ textDocument: { uri },
+ range: {
+ start: document.positionAt(contents.indexOf(needle)),
+ end: document.positionAt(contents.indexOf(needle)),
+ },
+ context: { diagnostics: [] },
+ };
+ }
+
+ beforeEach(() => {
+ documentManager = new DocumentManager();
+ diagnosticsManager = new DiagnosticsManager({ sendDiagnostics: vi.fn() } as any);
+ documentManager.open(uri, contents, version);
+ provider = new DisableCheckProvider(documentManager, diagnosticsManager);
+ });
+
+ it('offers "disable for this line" inserting a disable-next-line comment above the offense, indent-matched', () => {
+ diagnosticsManager.set(uri, version, [makeOffense('UnusedAssign', '{% assign x = 1 %}')]);
+
+ const codeActions = provider.codeActions(cursorAt('assign x = 1'));
+ const action = codeActions.find((a) => a.title === 'Disable UnusedAssign for this line');
+
+ expect(action).toEqual({
+ title: 'Disable UnusedAssign for this line',
+ kind: 'quickfix',
+ diagnostics: expect.any(Array),
+ isPreferred: false,
+ edit: {
+ changes: {
+ [uri]: [
+ {
+ range: {
+ start: { line: 1, character: 0 },
+ end: { line: 1, character: 0 },
+ },
+ newText: ' {% # theme-check-disable-next-line UnusedAssign %}\n',
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ it('offers no actions when the cursor does not overlap any offense', () => {
+ diagnosticsManager.set(uri, version, [
+ makeOffense('ParserBlockingScript', ''),
+ ]);
+
+ const codeActions = provider.codeActions(cursorAt('assign x = 1'));
+
+ expect(codeActions.length).toBe(0);
+ });
+});
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
new file mode 100644
index 000000000..6580c27cc
--- /dev/null
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
@@ -0,0 +1,58 @@
+import { SourceCodeType } from '@shopify/theme-check-common';
+import {
+ CodeAction,
+ CodeActionKind,
+ CodeActionParams,
+ Command,
+ TextEdit,
+ WorkspaceEdit,
+} from 'vscode-languageserver';
+import { TextDocument } from 'vscode-languageserver-textdocument';
+import { BaseCodeActionsProvider } from '../BaseCodeActionsProvider';
+import { isInRange, toEditCodeAction } from './utils';
+
+export class DisableCheckProvider extends BaseCodeActionsProvider {
+ static kind = CodeActionKind.QuickFix;
+
+ codeActions(params: CodeActionParams): (Command | CodeAction)[] {
+ const { uri } = params.textDocument;
+ const document = this.documentManager.get(uri);
+ const diagnostics = this.diagnosticsManager.get(uri);
+ if (!document || !diagnostics || document.type !== SourceCodeType.LiquidHtml) return [];
+
+ const { textDocument } = document;
+ const { anomalies } = diagnostics;
+ const start = textDocument.offsetAt(params.range.start);
+ const end = textDocument.offsetAt(params.range.end);
+
+ const anomaliesUnderCursor = anomalies.filter((anomaly) => isInRange(anomaly, start, end));
+ if (anomaliesUnderCursor.length === 0) return [];
+
+ const { offense, diagnostic } = anomaliesUnderCursor[0];
+ const check = offense.check;
+
+ return [
+ toEditCodeAction(
+ `Disable ${check} for this line`,
+ disableNextLineEdit(uri, textDocument, offense.start.line, check),
+ [diagnostic],
+ DisableCheckProvider.kind,
+ ),
+ ];
+ }
+}
+
+function disableNextLineEdit(
+ uri: string,
+ textDocument: TextDocument,
+ line: number,
+ check: string,
+): WorkspaceEdit {
+ const lineText = textDocument.getText({
+ start: { line, character: 0 },
+ end: { line: line + 1, character: 0 },
+ });
+ const indent = lineText.match(/^[ \t]*/)?.[0] ?? '';
+ const newText = `${indent}{% # theme-check-disable-next-line ${check} %}\n`;
+ return { changes: { [uri]: [TextEdit.insert({ line, character: 0 }, newText)] } };
+}
diff --git a/packages/theme-language-server-common/src/codeActions/providers/index.ts b/packages/theme-language-server-common/src/codeActions/providers/index.ts
index acd48a4aa..4c3399050 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/index.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/index.ts
@@ -1,3 +1,4 @@
+export { DisableCheckProvider } from './DisableCheckProvider';
export { FixProvider } from './FixProvider';
export { FixAllProvider } from './FixAllProvider';
export { SuggestionProvider } from './SuggestionProvider';
diff --git a/packages/theme-language-server-common/src/codeActions/providers/utils.ts b/packages/theme-language-server-common/src/codeActions/providers/utils.ts
index 3bcab93d9..778764e78 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/utils.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/utils.ts
@@ -1,5 +1,11 @@
import { Offense, SourceCodeType, WithRequired } from '@shopify/theme-check-common';
-import { CodeAction, CodeActionKind, Command, Diagnostic } from 'vscode-languageserver';
+import {
+ CodeAction,
+ CodeActionKind,
+ Command,
+ Diagnostic,
+ WorkspaceEdit,
+} from 'vscode-languageserver';
import { Anomaly } from '../../diagnostics';
// They have an awkard API for creating them, so we have this helper here
@@ -17,6 +23,22 @@ export function toCodeAction(
return codeAction;
}
+// Edit-based sibling of toCodeAction: the action carries a WorkspaceEdit
+// directly instead of a server command.
+export function toEditCodeAction(
+ title: string,
+ edit: WorkspaceEdit,
+ diagnostics: Diagnostic[],
+ kind: CodeActionKind,
+ isPreferred: boolean = false,
+): CodeAction {
+ const codeAction = CodeAction.create(title, kind);
+ codeAction.edit = edit;
+ codeAction.diagnostics = diagnostics;
+ codeAction.isPreferred = isPreferred;
+ return codeAction;
+}
+
/**
* The range is either the selection or cursor position, an offense is in
* range if the selection and offense overlap in any way.
From 1f48c9b6fd5daedf3b0d246870c3d4e256655659 Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:01:11 +0800
Subject: [PATCH 2/7] refactor(theme-language-server-common): tidy
disable-check export order + drop redundant test cast
---
.../src/codeActions/providers/DisableCheckProvider.spec.ts | 2 +-
.../src/codeActions/providers/index.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index cb9142d74..4e163a233 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -33,7 +33,7 @@ describe('Unit: DisableCheckProvider', () => {
severity: Severity.ERROR,
start: { ...document.positionAt(start), index: start },
end: { ...document.positionAt(end), index: end },
- } as Offense;
+ };
}
function cursorAt(needle: string) {
diff --git a/packages/theme-language-server-common/src/codeActions/providers/index.ts b/packages/theme-language-server-common/src/codeActions/providers/index.ts
index 4c3399050..e17180e44 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/index.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/index.ts
@@ -1,4 +1,4 @@
-export { DisableCheckProvider } from './DisableCheckProvider';
export { FixProvider } from './FixProvider';
export { FixAllProvider } from './FixAllProvider';
export { SuggestionProvider } from './SuggestionProvider';
+export { DisableCheckProvider } from './DisableCheckProvider';
From 989abbedc261cb4291c6a7b306ce2f0cc54f3ffe Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:02:21 +0800
Subject: [PATCH 3/7] feat(theme-language-server-common): add disable-check
'entire file' code action
---
.../providers/DisableCheckProvider.spec.ts | 27 +++++++++++++++++++
.../providers/DisableCheckProvider.ts | 11 ++++++++
2 files changed, 38 insertions(+)
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index 4e163a233..893167d37 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -81,6 +81,33 @@ describe('Unit: DisableCheckProvider', () => {
});
});
+ it('offers "disable for entire file" inserting a disable comment at the top of the file', () => {
+ diagnosticsManager.set(uri, version, [makeOffense('UnusedAssign', '{% assign x = 1 %}')]);
+
+ const codeActions = provider.codeActions(cursorAt('assign x = 1'));
+ const action = codeActions.find((a) => a.title === 'Disable UnusedAssign for entire file');
+
+ expect(action).toEqual({
+ title: 'Disable UnusedAssign for entire file',
+ kind: 'quickfix',
+ diagnostics: expect.any(Array),
+ isPreferred: false,
+ edit: {
+ changes: {
+ [uri]: [
+ {
+ range: {
+ start: { line: 0, character: 0 },
+ end: { line: 0, character: 0 },
+ },
+ newText: '{% # theme-check-disable UnusedAssign %}\n',
+ },
+ ],
+ },
+ },
+ });
+ });
+
it('offers no actions when the cursor does not overlap any offense', () => {
diagnosticsManager.set(uri, version, [
makeOffense('ParserBlockingScript', ''),
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
index 6580c27cc..abc02ecf3 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
@@ -38,10 +38,21 @@ export class DisableCheckProvider extends BaseCodeActionsProvider {
[diagnostic],
DisableCheckProvider.kind,
),
+ toEditCodeAction(
+ `Disable ${check} for entire file`,
+ disableFileEdit(uri, check),
+ [diagnostic],
+ DisableCheckProvider.kind,
+ ),
];
}
}
+function disableFileEdit(uri: string, check: string): WorkspaceEdit {
+ const newText = `{% # theme-check-disable ${check} %}\n`;
+ return { changes: { [uri]: [TextEdit.insert({ line: 0, character: 0 }, newText)] } };
+}
+
function disableNextLineEdit(
uri: string,
textDocument: TextDocument,
From 3b54dcb797ca14f8f0e45a36f33c6002b2d651a8 Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:06:24 +0800
Subject: [PATCH 4/7] feat(theme-language-server-common): group disable-check
actions by check name
---
.../providers/DisableCheckProvider.spec.ts | 45 ++++++++++++++++++
.../providers/DisableCheckProvider.ts | 47 ++++++++++++-------
2 files changed, 75 insertions(+), 17 deletions(-)
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index 893167d37..dc76e4a24 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -47,6 +47,17 @@ describe('Unit: DisableCheckProvider', () => {
};
}
+ function rangeFromTo(startNeedle: string, endNeedle: string) {
+ return {
+ textDocument: { uri },
+ range: {
+ start: document.positionAt(contents.indexOf(startNeedle)),
+ end: document.positionAt(contents.indexOf(endNeedle) + endNeedle.length),
+ },
+ context: { diagnostics: [] },
+ };
+ }
+
beforeEach(() => {
documentManager = new DocumentManager();
diagnosticsManager = new DiagnosticsManager({ sendDiagnostics: vi.fn() } as any);
@@ -117,4 +128,38 @@ describe('Unit: DisableCheckProvider', () => {
expect(codeActions.length).toBe(0);
});
+
+ it('emits only one action pair per check when several offenses of the same check are selected', () => {
+ diagnosticsManager.set(uri, version, [
+ makeOffense('ParserBlockingScript', ''),
+ makeOffense('ParserBlockingScript', ''),
+ ]);
+
+ const codeActions = provider.codeActions(rangeFromTo('2.js', '3.js'));
+
+ expect(codeActions.length).toBe(2);
+ expect(codeActions.map((a) => a.title)).toEqual([
+ 'Disable ParserBlockingScript for this line',
+ 'Disable ParserBlockingScript for entire file',
+ ]);
+ });
+
+ it('emits one action pair per distinct check when multiple checks are selected', () => {
+ diagnosticsManager.set(uri, version, [
+ makeOffense('UnusedAssign', '{% assign x = 1 %}'),
+ makeOffense('ParserBlockingScript', ''),
+ ]);
+
+ const codeActions = provider.codeActions(rangeFromTo('assign x = 1', '2.js'));
+
+ expect(codeActions.length).toBe(4);
+ expect(codeActions.map((a) => a.title).sort()).toEqual(
+ [
+ 'Disable ParserBlockingScript for entire file',
+ 'Disable ParserBlockingScript for this line',
+ 'Disable UnusedAssign for entire file',
+ 'Disable UnusedAssign for this line',
+ ].sort(),
+ );
+ });
});
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
index abc02ecf3..930d01b5c 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
@@ -1,9 +1,10 @@
-import { SourceCodeType } from '@shopify/theme-check-common';
+import { Offense, SourceCodeType } from '@shopify/theme-check-common';
import {
CodeAction,
CodeActionKind,
CodeActionParams,
Command,
+ Diagnostic,
TextEdit,
WorkspaceEdit,
} from 'vscode-languageserver';
@@ -28,23 +29,35 @@ export class DisableCheckProvider extends BaseCodeActionsProvider {
const anomaliesUnderCursor = anomalies.filter((anomaly) => isInRange(anomaly, start, end));
if (anomaliesUnderCursor.length === 0) return [];
- const { offense, diagnostic } = anomaliesUnderCursor[0];
- const check = offense.check;
+ const byCheck = new Map();
+ for (const { offense, diagnostic } of anomaliesUnderCursor) {
+ const existing = byCheck.get(offense.check);
+ if (existing) {
+ existing.diagnostics.push(diagnostic);
+ } else {
+ byCheck.set(offense.check, { offense, diagnostics: [diagnostic] });
+ }
+ }
- return [
- toEditCodeAction(
- `Disable ${check} for this line`,
- disableNextLineEdit(uri, textDocument, offense.start.line, check),
- [diagnostic],
- DisableCheckProvider.kind,
- ),
- toEditCodeAction(
- `Disable ${check} for entire file`,
- disableFileEdit(uri, check),
- [diagnostic],
- DisableCheckProvider.kind,
- ),
- ];
+ const actions: CodeAction[] = [];
+ for (const [check, { offense, diagnostics: groupDiagnostics }] of byCheck) {
+ actions.push(
+ toEditCodeAction(
+ `Disable ${check} for this line`,
+ disableNextLineEdit(uri, textDocument, offense.start.line, check),
+ groupDiagnostics,
+ DisableCheckProvider.kind,
+ ),
+ toEditCodeAction(
+ `Disable ${check} for entire file`,
+ disableFileEdit(uri, check),
+ groupDiagnostics,
+ DisableCheckProvider.kind,
+ ),
+ );
+ }
+
+ return actions;
}
}
From bc5cb2861ed544f7243650bd935ceeb7b854ea9b Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:12:40 +0800
Subject: [PATCH 5/7] feat(theme-language-server-common): skip
disable-next-line action inside {% liquid %} blocks
---
.../providers/DisableCheckProvider.spec.ts | 48 +++++++++++++++++++
.../providers/DisableCheckProvider.ts | 27 ++++++++---
2 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index dc76e4a24..504f012e2 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -163,3 +163,51 @@ describe('Unit: DisableCheckProvider', () => {
);
});
});
+
+describe('Unit: DisableCheckProvider — {% liquid %} blocks', () => {
+ const uri = path.normalize(URI.file('/path/to/file.liquid'));
+ const contents = `{% liquid
+ assign x = 1
+%}`;
+ const version = 0;
+ const document = TextDocument.create(uri, 'liquid', version, contents);
+ let documentManager: DocumentManager;
+ let diagnosticsManager: DiagnosticsManager;
+ let provider: DisableCheckProvider;
+
+ function makeOffense(checkName: string, needle: string): Offense {
+ const start = contents.indexOf(needle);
+ const end = start + needle.length;
+ return {
+ type: SourceCodeType.LiquidHtml,
+ check: checkName,
+ message: `${checkName} problem`,
+ uri: 'file:///path/to/file.liquid',
+ severity: Severity.ERROR,
+ start: { ...document.positionAt(start), index: start },
+ end: { ...document.positionAt(end), index: end },
+ };
+ }
+
+ beforeEach(() => {
+ documentManager = new DocumentManager();
+ diagnosticsManager = new DiagnosticsManager({ sendDiagnostics: vi.fn() } as any);
+ documentManager.open(uri, contents, version);
+ provider = new DisableCheckProvider(documentManager, diagnosticsManager);
+ });
+
+ it('does not offer "this line" inside a {% liquid %} block, but still offers "entire file"', () => {
+ diagnosticsManager.set(uri, version, [makeOffense('UnusedAssign', 'assign x = 1')]);
+
+ const codeActions = provider.codeActions({
+ textDocument: { uri },
+ range: {
+ start: document.positionAt(contents.indexOf('assign x = 1')),
+ end: document.positionAt(contents.indexOf('assign x = 1')),
+ },
+ context: { diagnostics: [] },
+ });
+
+ expect(codeActions.map((a) => a.title)).toEqual(['Disable UnusedAssign for entire file']);
+ });
+});
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
index 930d01b5c..e0dd8cf74 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
@@ -1,4 +1,5 @@
-import { Offense, SourceCodeType } from '@shopify/theme-check-common';
+import { Offense, SourceCodeType, findCurrentNode, isError } from '@shopify/theme-check-common';
+import { LiquidHtmlNode, NodeTypes } from '@shopify/liquid-html-parser';
import {
CodeAction,
CodeActionKind,
@@ -41,13 +42,17 @@ export class DisableCheckProvider extends BaseCodeActionsProvider {
const actions: CodeAction[] = [];
for (const [check, { offense, diagnostics: groupDiagnostics }] of byCheck) {
+ if (!isInsideLiquidTag(document.ast, offense.start.index)) {
+ actions.push(
+ toEditCodeAction(
+ `Disable ${check} for this line`,
+ disableNextLineEdit(uri, textDocument, offense.start.line, check),
+ groupDiagnostics,
+ DisableCheckProvider.kind,
+ ),
+ );
+ }
actions.push(
- toEditCodeAction(
- `Disable ${check} for this line`,
- disableNextLineEdit(uri, textDocument, offense.start.line, check),
- groupDiagnostics,
- DisableCheckProvider.kind,
- ),
toEditCodeAction(
`Disable ${check} for entire file`,
disableFileEdit(uri, check),
@@ -80,3 +85,11 @@ function disableNextLineEdit(
const newText = `${indent}{% # theme-check-disable-next-line ${check} %}\n`;
return { changes: { [uri]: [TextEdit.insert({ line, character: 0 }, newText)] } };
}
+
+function isInsideLiquidTag(ast: LiquidHtmlNode | Error, index: number): boolean {
+ if (isError(ast)) return false;
+ const [currentNode, ancestors] = findCurrentNode(ast, index);
+ return [currentNode, ...ancestors].some(
+ (node) => node.type === NodeTypes.LiquidTag && node.name === 'liquid',
+ );
+}
From 178c3bcf7d6fefd4518cbdb88cd673d19edc9348 Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:19:54 +0800
Subject: [PATCH 6/7] refactor(theme-language-server-common): use
NamedTags.liquid + clearer helper name, add outside-liquid test
---
.../providers/DisableCheckProvider.spec.ts | 21 ++++++++++++++++++-
.../providers/DisableCheckProvider.ts | 8 +++----
2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index 504f012e2..d83411e90 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -168,7 +168,8 @@ describe('Unit: DisableCheckProvider — {% liquid %} blocks', () => {
const uri = path.normalize(URI.file('/path/to/file.liquid'));
const contents = `{% liquid
assign x = 1
-%}`;
+%}
+{% assign y = 2 %}`;
const version = 0;
const document = TextDocument.create(uri, 'liquid', version, contents);
let documentManager: DocumentManager;
@@ -210,4 +211,22 @@ describe('Unit: DisableCheckProvider — {% liquid %} blocks', () => {
expect(codeActions.map((a) => a.title)).toEqual(['Disable UnusedAssign for entire file']);
});
+
+ it('still offers "this line" for an offense outside the {% liquid %} block in the same file', () => {
+ diagnosticsManager.set(uri, version, [makeOffense('UnusedAssign', 'assign y = 2')]);
+
+ const codeActions = provider.codeActions({
+ textDocument: { uri },
+ range: {
+ start: document.positionAt(contents.indexOf('assign y = 2')),
+ end: document.positionAt(contents.indexOf('assign y = 2')),
+ },
+ context: { diagnostics: [] },
+ });
+
+ expect(codeActions.map((a) => a.title)).toEqual([
+ 'Disable UnusedAssign for this line',
+ 'Disable UnusedAssign for entire file',
+ ]);
+ });
});
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
index e0dd8cf74..c260cfc37 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.ts
@@ -1,5 +1,5 @@
import { Offense, SourceCodeType, findCurrentNode, isError } from '@shopify/theme-check-common';
-import { LiquidHtmlNode, NodeTypes } from '@shopify/liquid-html-parser';
+import { LiquidHtmlNode, NamedTags, NodeTypes } from '@shopify/liquid-html-parser';
import {
CodeAction,
CodeActionKind,
@@ -42,7 +42,7 @@ export class DisableCheckProvider extends BaseCodeActionsProvider {
const actions: CodeAction[] = [];
for (const [check, { offense, diagnostics: groupDiagnostics }] of byCheck) {
- if (!isInsideLiquidTag(document.ast, offense.start.index)) {
+ if (!isInsideLiquidLiquidTag(document.ast, offense.start.index)) {
actions.push(
toEditCodeAction(
`Disable ${check} for this line`,
@@ -86,10 +86,10 @@ function disableNextLineEdit(
return { changes: { [uri]: [TextEdit.insert({ line, character: 0 }, newText)] } };
}
-function isInsideLiquidTag(ast: LiquidHtmlNode | Error, index: number): boolean {
+function isInsideLiquidLiquidTag(ast: LiquidHtmlNode | Error, index: number): boolean {
if (isError(ast)) return false;
const [currentNode, ancestors] = findCurrentNode(ast, index);
return [currentNode, ...ancestors].some(
- (node) => node.type === NodeTypes.LiquidTag && node.name === 'liquid',
+ (node) => node.type === NodeTypes.LiquidTag && node.name === NamedTags.liquid,
);
}
From 5151a34fc2345c69671470b22020556a3fb8f608 Mon Sep 17 00:00:00 2001
From: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Date: Fri, 29 May 2026 10:21:02 +0800
Subject: [PATCH 7/7] chore(theme-language-server-common): add changeset +
prettier formatting for disable-check code action
---
.changeset/add-disable-check-code-action.md | 5 +++++
.../src/codeActions/CodeActionsProvider.ts | 7 +------
.../src/codeActions/providers/DisableCheckProvider.spec.ts | 5 +----
3 files changed, 7 insertions(+), 10 deletions(-)
create mode 100644 .changeset/add-disable-check-code-action.md
diff --git a/.changeset/add-disable-check-code-action.md b/.changeset/add-disable-check-code-action.md
new file mode 100644
index 000000000..440e110ff
--- /dev/null
+++ b/.changeset/add-disable-check-code-action.md
@@ -0,0 +1,5 @@
+---
+'@shopify/theme-language-server-common': minor
+---
+
+Add code actions to disable a theme-check check for the current line or the entire file. When a theme-check diagnostic is under the cursor, two quick-fixes are offered that insert the corresponding `theme-check-disable-next-line` / `theme-check-disable` magic comment for you.
diff --git a/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts b/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
index ab1940668..c2ceb017b 100644
--- a/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
+++ b/packages/theme-language-server-common/src/codeActions/CodeActionsProvider.ts
@@ -1,12 +1,7 @@
import { CodeAction, CodeActionParams, Command } from 'vscode-languageserver';
import { DiagnosticsManager } from '../diagnostics';
import { DocumentManager } from '../documents';
-import {
- DisableCheckProvider,
- FixAllProvider,
- FixProvider,
- SuggestionProvider,
-} from './providers';
+import { DisableCheckProvider, FixAllProvider, FixProvider, SuggestionProvider } from './providers';
import { BaseCodeActionsProvider } from './BaseCodeActionsProvider';
export const CodeActionKinds = Array.from(
diff --git a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
index d83411e90..0460ff347 100644
--- a/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
+++ b/packages/theme-language-server-common/src/codeActions/providers/DisableCheckProvider.spec.ts
@@ -19,10 +19,7 @@ describe('Unit: DisableCheckProvider', () => {
let diagnosticsManager: DiagnosticsManager;
let provider: DisableCheckProvider;
- function makeOffense(
- checkName: string,
- needle: string,
- ): Offense {
+ function makeOffense(checkName: string, needle: string): Offense {
const start = contents.indexOf(needle);
const end = start + needle.length;
return {