mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
chore(lint): enable obsidianmd/no-global-this and fix violations (#2413)
Removes the deferred `"off"` override and rewrites the 55 violations (all in test/mocks/Node-script files) to use `window.*` instead of `global.*`. Production code was already cleaned up in #2401. - 18 test files + __mocks__/obsidian.js + jest.setup.js: mechanical `global.X` → `window.X` (Jest runs under jsdom, so window === globalThis) - scripts/printPromptDebugEntry.ts: inline eslint-disable — Node-only debug script with no window available Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
48794ce097
commit
710acc2436
20 changed files with 55 additions and 57 deletions
|
|
@ -82,7 +82,7 @@ module.exports = {
|
|||
};
|
||||
|
||||
// Mock the global app object
|
||||
global.app = {
|
||||
window.app = {
|
||||
vault: {
|
||||
getAbstractFileByPath: jest.fn().mockReturnValue({
|
||||
name: "test-file.md",
|
||||
|
|
|
|||
|
|
@ -103,9 +103,6 @@ export default [
|
|||
// no-deprecated: defer — surface the warnings, but don't fail CI yet
|
||||
"@typescript-eslint/no-deprecated": "off",
|
||||
|
||||
// obsidianmd/no-global-this fires on jest globalThis usage in tests and
|
||||
// a small number of legit cases — defer triage.
|
||||
"obsidianmd/no-global-this": "off",
|
||||
// rule-custom-message wraps no-console; we don't enable no-console in this PR
|
||||
// and the codebase enforces logInfo/logWarn/logError via CLAUDE.md.
|
||||
"obsidianmd/rule-custom-message": "off",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import "web-streams-polyfill/dist/polyfill.min.js";
|
||||
import { TextEncoder, TextDecoder } from "util";
|
||||
|
||||
global.TextEncoder = TextEncoder;
|
||||
global.TextDecoder = TextDecoder;
|
||||
window.TextEncoder = TextEncoder;
|
||||
window.TextDecoder = TextDecoder;
|
||||
|
||||
// Polyfill Obsidian's Node.doc / Node.win augmentation so plugin code that
|
||||
// reads `element.doc` / `element.win` works under jsdom.
|
||||
if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.prototype, "doc")) {
|
||||
Object.defineProperty(Node.prototype, "doc", {
|
||||
get() {
|
||||
return this.ownerDocument ?? global.document;
|
||||
return this.ownerDocument ?? window.document;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
|
@ -17,7 +17,7 @@ if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.pr
|
|||
if (typeof Node !== "undefined" && !Object.prototype.hasOwnProperty.call(Node.prototype, "win")) {
|
||||
Object.defineProperty(Node.prototype, "win", {
|
||||
get() {
|
||||
return this.ownerDocument?.defaultView ?? global.window;
|
||||
return this.ownerDocument?.defaultView ?? window;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ export async function run(args: string[]): Promise<void> {
|
|||
}
|
||||
|
||||
const app = createHeadlessApp();
|
||||
// eslint-disable-next-line obsidianmd/no-global-this -- node-only debug script, no window available
|
||||
(global as unknown as { app: unknown }).app = app;
|
||||
|
||||
initializeBuiltinTools();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe("Image extraction from content", () => {
|
|||
},
|
||||
};
|
||||
|
||||
(global as any).app = mockApp;
|
||||
(window as any).app = mockApp;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ jest.mock("@/logger", () => ({
|
|||
|
||||
// Mock global fetch
|
||||
const mockFetch = jest.fn();
|
||||
global.fetch = mockFetch;
|
||||
window.fetch = mockFetch;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
|||
|
|
@ -670,7 +670,7 @@ describe("parseCustomCommandFile", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
// Save and mock global app
|
||||
originalApp = global.app;
|
||||
originalApp = window.app;
|
||||
mockFrontmatter = {
|
||||
"copilot-command-context-menu-enabled": true,
|
||||
"copilot-command-slash-enabled": false,
|
||||
|
|
@ -679,7 +679,7 @@ describe("parseCustomCommandFile", () => {
|
|||
"copilot-command-last-used": 1234567890,
|
||||
};
|
||||
mockMetadata = { frontmatter: mockFrontmatter };
|
||||
global.app = {
|
||||
window.app = {
|
||||
vault: {
|
||||
read: jest
|
||||
.fn()
|
||||
|
|
@ -699,7 +699,7 @@ describe("parseCustomCommandFile", () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.app = originalApp;
|
||||
window.app = originalApp;
|
||||
});
|
||||
|
||||
it("parses a custom command file with frontmatter and content", async () => {
|
||||
|
|
@ -719,8 +719,8 @@ describe("parseCustomCommandFile", () => {
|
|||
});
|
||||
|
||||
it("uses EMPTY_COMMAND defaults if frontmatter is missing", async () => {
|
||||
(global.app.vault as any).read.mockResolvedValue("Prompt content only, no frontmatter.");
|
||||
(global.app.metadataCache as any).getFileCache.mockReturnValue({});
|
||||
(window.app.vault as any).read.mockResolvedValue("Prompt content only, no frontmatter.");
|
||||
(window.app.metadataCache as any).getFileCache.mockReturnValue({});
|
||||
const { parseCustomCommandFile } = await import("@/commands/customCommandUtils");
|
||||
const result = await parseCustomCommandFile(mockFile);
|
||||
expect(result).toEqual({
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const mockApp = {
|
|||
};
|
||||
|
||||
// Mock global app
|
||||
Object.defineProperty(global, "app", {
|
||||
Object.defineProperty(window, "app", {
|
||||
value: mockApp,
|
||||
writable: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { ContextProcessor } from "@/contextProcessor";
|
|||
import { DATAVIEW_BLOCK_TAG } from "@/constants";
|
||||
|
||||
// Mock the global app object for Dataview plugin access
|
||||
global.app = {
|
||||
window.app = {
|
||||
plugins: {
|
||||
plugins: {},
|
||||
},
|
||||
|
|
@ -25,7 +25,7 @@ describe("ContextProcessor - Dataview Integration", () => {
|
|||
beforeEach(() => {
|
||||
contextProcessor = ContextProcessor.getInstance();
|
||||
// Reset plugins for each test
|
||||
(global.app as any).plugins.plugins = {};
|
||||
(window.app as any).plugins.plugins = {};
|
||||
// Save and mock console.error to suppress expected error messages
|
||||
originalConsoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
|
|
@ -44,7 +44,7 @@ describe("ContextProcessor - Dataview Integration", () => {
|
|||
});
|
||||
|
||||
it("should return content unchanged when Dataview API is not available", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {};
|
||||
(window.app as any).plugins.plugins.dataview = {};
|
||||
const content = "```dataview\nLIST\n```";
|
||||
const result = await contextProcessor.processDataviewBlocks(content, "test.md");
|
||||
expect(result).toBe(content);
|
||||
|
|
@ -54,7 +54,7 @@ describe("ContextProcessor - Dataview Integration", () => {
|
|||
describe("processDataviewBlocks - Regex Pattern Matching", () => {
|
||||
beforeEach(() => {
|
||||
// Mock successful Dataview plugin with API
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -105,7 +105,7 @@ describe("ContextProcessor - Dataview Integration", () => {
|
|||
|
||||
describe("processDataviewBlocks - Multiple Blocks", () => {
|
||||
beforeEach(() => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest
|
||||
.fn()
|
||||
|
|
@ -172,7 +172,7 @@ LIST
|
|||
|
||||
describe("processDataviewBlocks - Query Execution", () => {
|
||||
it("should include both original query and executed results", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -194,7 +194,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should handle query timeout gracefully", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockImplementation(
|
||||
() =>
|
||||
|
|
@ -213,7 +213,7 @@ LIST
|
|||
}, 10000); // Increase test timeout to 10s
|
||||
|
||||
it("should handle query execution errors", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: false,
|
||||
|
|
@ -229,7 +229,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should handle dataviewjs with unsupported message", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn(),
|
||||
},
|
||||
|
|
@ -244,7 +244,7 @@ LIST
|
|||
|
||||
describe("processDataviewBlocks - Result Formatting", () => {
|
||||
it("should format LIST results correctly", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -265,7 +265,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should format TABLE results correctly", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -291,7 +291,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should format TASK results correctly", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -314,7 +314,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should handle empty results", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -333,7 +333,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should handle null values gracefully", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -354,7 +354,7 @@ LIST
|
|||
});
|
||||
|
||||
it("should handle arrays in values", async () => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
@ -375,7 +375,7 @@ LIST
|
|||
|
||||
describe("processDataviewBlocks - Edge Cases", () => {
|
||||
beforeEach(() => {
|
||||
(global.app as any).plugins.plugins.dataview = {
|
||||
(window.app as any).plugins.plugins.dataview = {
|
||||
api: {
|
||||
query: jest.fn().mockResolvedValue({
|
||||
successful: true,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe("ContextProcessor - Embedded Notes", () => {
|
|||
getFileCache: jest.fn((file: TFile) => fileCaches[file.path] ?? {}),
|
||||
};
|
||||
|
||||
(global as any).app = {
|
||||
(window as any).app = {
|
||||
metadataCache: metadataCacheMock,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ const mockElectron = {
|
|||
|
||||
jest.mock("electron", () => mockElectron);
|
||||
|
||||
global.TextEncoder = TextEncoder as any;
|
||||
global.TextDecoder = TextDecoder as any;
|
||||
window.TextEncoder = TextEncoder as any;
|
||||
window.TextDecoder = TextDecoder as any;
|
||||
|
||||
// Now we can import our modules
|
||||
import { encryptAllKeys, getDecryptedKey, getEncryptedKey } from "@/encryptionService";
|
||||
|
|
@ -25,8 +25,8 @@ import { Platform } from "obsidian";
|
|||
import { Buffer } from "buffer";
|
||||
|
||||
// Mock window.btoa and window.atob for base64 encoding/decoding
|
||||
global.btoa = jest.fn().mockImplementation((str) => Buffer.from(str).toString("base64"));
|
||||
global.atob = jest.fn().mockImplementation((str) => Buffer.from(str, "base64").toString());
|
||||
window.btoa = jest.fn().mockImplementation((str) => Buffer.from(str).toString("base64"));
|
||||
window.atob = jest.fn().mockImplementation((str) => Buffer.from(str, "base64").toString());
|
||||
|
||||
const mockSubtle = {
|
||||
importKey: jest.fn().mockResolvedValue("mockCryptoKey"),
|
||||
|
|
@ -43,7 +43,7 @@ const mockSubtle = {
|
|||
};
|
||||
|
||||
// Mock crypto.subtle instead of the entire crypto object
|
||||
Object.defineProperty(global.crypto, "subtle", {
|
||||
Object.defineProperty(window.crypto, "subtle", {
|
||||
value: mockSubtle,
|
||||
configurable: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function makeMockFile(path: string): TFile {
|
|||
|
||||
// Helper: set up the global `app` mock used by parseProjectConfigFile
|
||||
function setupAppMock(rawContent: string, frontmatter: Record<string, unknown> | null) {
|
||||
(global as Record<string, unknown>).app = {
|
||||
(window as unknown as Record<string, unknown>).app = {
|
||||
vault: {
|
||||
read: jest.fn().mockResolvedValue(rawContent),
|
||||
// Reason: parseProjectConfigFile uses `cachedFile instanceof TFile` to detect synthetic TFiles.
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ describe("findRelevantNotes", () => {
|
|||
["linked-only.md", linkedOnly],
|
||||
]);
|
||||
|
||||
(global.app.vault.getAbstractFileByPath as jest.Mock).mockImplementation((path: string) => {
|
||||
(window.app.vault.getAbstractFileByPath as jest.Mock).mockImplementation((path: string) => {
|
||||
return filesByPath.get(path) ?? null;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -68,12 +68,12 @@ jest.mock("@/settings/model", () => ({
|
|||
describe("searchUtils", () => {
|
||||
beforeAll(() => {
|
||||
// @ts-ignore
|
||||
global.app = mockApp;
|
||||
window.app = mockApp;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// @ts-ignore
|
||||
delete global.app;
|
||||
delete window.app;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -75,14 +75,14 @@ describe("migrateSystemPromptsFromSettings", () => {
|
|||
} as unknown as Vault;
|
||||
|
||||
// Mock global app
|
||||
originalApp = global.app;
|
||||
global.app = {
|
||||
originalApp = window.app;
|
||||
window.app = {
|
||||
vault: mockVault,
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.app = originalApp;
|
||||
window.app = originalApp;
|
||||
});
|
||||
|
||||
it("skips migration when userSystemPrompt is empty", async () => {
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ describe("SystemPromptManager", () => {
|
|||
} as unknown as Vault;
|
||||
|
||||
// Mock global app
|
||||
originalApp = global.app;
|
||||
global.app = {
|
||||
originalApp = window.app;
|
||||
window.app = {
|
||||
vault: mockVault,
|
||||
fileManager: {
|
||||
processFrontMatter: jest.fn(),
|
||||
|
|
@ -81,7 +81,7 @@ describe("SystemPromptManager", () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.app = originalApp;
|
||||
window.app = originalApp;
|
||||
});
|
||||
|
||||
describe("getInstance", () => {
|
||||
|
|
|
|||
|
|
@ -250,14 +250,14 @@ describe("parseSystemPromptFile", () => {
|
|||
let mockFile: TFile;
|
||||
|
||||
beforeEach(() => {
|
||||
originalApp = global.app;
|
||||
originalApp = window.app;
|
||||
mockFile = mockTFile({
|
||||
basename: "Test Prompt",
|
||||
path: "SystemPrompts/Test Prompt.md",
|
||||
extension: "md",
|
||||
});
|
||||
|
||||
global.app = {
|
||||
window.app = {
|
||||
vault: {
|
||||
read: jest.fn(),
|
||||
},
|
||||
|
|
@ -268,7 +268,7 @@ describe("parseSystemPromptFile", () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.app = originalApp;
|
||||
window.app = originalApp;
|
||||
});
|
||||
|
||||
it("parses a file with frontmatter and content", async () => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ describe("ProjectContextCache", () => {
|
|||
jest.clearAllMocks();
|
||||
|
||||
// Mock globals
|
||||
global.app = mockApp as any;
|
||||
window.app = mockApp as any;
|
||||
|
||||
// Set up mock vault file retrieval
|
||||
mockApp.vault.getFiles.mockReturnValue([mockMarkdownFile, mockPdfFile]);
|
||||
|
|
|
|||
|
|
@ -35,14 +35,14 @@ describe("readNoteTool", () => {
|
|||
beforeEach(async () => {
|
||||
jest.resetModules();
|
||||
|
||||
originalApp = global.app;
|
||||
originalApp = window.app;
|
||||
getAbstractFileByPathMock = jest.fn();
|
||||
getMarkdownFilesMock = jest.fn().mockReturnValue([]);
|
||||
getFirstLinkpathDestMock = jest.fn().mockReturnValue(null);
|
||||
mockRead.mockReset();
|
||||
mockRead.mockResolvedValue("");
|
||||
|
||||
global.app = {
|
||||
window.app = {
|
||||
vault: {
|
||||
getAbstractFileByPath: getAbstractFileByPathMock,
|
||||
getMarkdownFiles: getMarkdownFilesMock,
|
||||
|
|
@ -62,7 +62,7 @@ describe("readNoteTool", () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.app = originalApp;
|
||||
window.app = originalApp;
|
||||
});
|
||||
|
||||
it("returns the first chunk with follow-up metadata", async () => {
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ describe("getNotesFromPath", () => {
|
|||
describe("getNotesFromTags", () => {
|
||||
beforeAll(() => {
|
||||
// @ts-ignore
|
||||
global.app = mockApp;
|
||||
window.app = mockApp;
|
||||
|
||||
// Setup metadata cache mock
|
||||
mockMetadataCache.getFileCache.mockImplementation((file: TFile) => {
|
||||
|
|
@ -275,7 +275,7 @@ describe("getNotesFromTags", () => {
|
|||
|
||||
afterAll(() => {
|
||||
// @ts-ignore
|
||||
delete global.app;
|
||||
delete window.app;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue