diff --git a/.github/workflows/unit-ci.yml b/.github/workflows/unit-ci.yml index 7bc53deb..fdc569d4 100644 --- a/.github/workflows/unit-ci.yml +++ b/.github/workflows/unit-ci.yml @@ -17,6 +17,8 @@ on: - 'vitest.config*.ts' - 'esbuild.config.mjs' - 'eslint.config.mjs' + - 'eslint.config.common.mjs' + - 'eslint.community.config.mjs' - 'update-workspaces.mjs' - 'version-bump.mjs' - 'utils/release-*.mjs' @@ -36,6 +38,8 @@ on: - 'vitest.config*.ts' - 'esbuild.config.mjs' - 'eslint.config.mjs' + - 'eslint.config.common.mjs' + - 'eslint.community.config.mjs' - 'update-workspaces.mjs' - 'version-bump.mjs' - 'utils/release-*.mjs' @@ -66,6 +70,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Run source checks + run: npm run check + - name: Run unit tests suite with coverage run: npm run test:unit:coverage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5f5e83d..a1ca3b9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,7 @@ Before submitting a pull request, you must run verification scripts locally to e ```bash npm run check ``` + This also type-checks the maintained CLI and browser applications, and applies the Community directory blocker rules. Run `npm run lint:community` separately to inspect its non-blocking recommendations. - Run unit tests: ```bash npm run test:unit diff --git a/eslint.community.config.mjs b/eslint.community.config.mjs new file mode 100644 index 00000000..f243f318 --- /dev/null +++ b/eslint.community.config.mjs @@ -0,0 +1,81 @@ +import obsidianmd from "eslint-plugin-obsidianmd"; +import globals from "globals"; +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig( + globalIgnores([ + "node_modules", + "**/dist", + "build", + "coverage", + "main.js", + "main_org.js", + "pouchdb-browser.js", + "package.json", + "package-lock.json", + "versions.json", + // Svelte is covered by the project lint and svelte-check; the directory report currently analyses TypeScript. + "**/*.svelte", + "**/svelte.config.js", + "**/*.unit.spec.ts", + "**/test/**", + "src/apps/_test/**", + "src/apps/cli/testdeno/**", + ]), + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + MANIFEST_VERSION: "readonly", + PACKAGE_VERSION: "readonly", + UPDATE_INFO: "readonly", + hostPlatform: "readonly", + }, + parserOptions: { + project: [ + "./tsconfig.json", + "./src/apps/browser/tsconfig.json", + "./src/apps/cli/tsconfig.json", + "./src/apps/webapp/tsconfig.json", + "./src/apps/webpeer/tsconfig.app.json", + "./src/apps/webpeer/tsconfig.node.json", + ], + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + ...obsidianmd.configs.recommended, + { + rules: { + // The directory review reports console usage as guidance rather than a release blocker. + "obsidianmd/rule-custom-message": "off", + "no-console": "warn", + "obsidianmd/no-unsupported-api": "error", + // Keep legacy type-safety debt visible while reserving errors for directory-review blockers. + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-base-to-string": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + }, + }, + { + files: ["src/apps/**/*.{ts,js,mjs}"], + rules: { + // These applications are inspected by the directory review but accept external command and file data. + "@typescript-eslint/restrict-template-expressions": "warn", + }, + }, + { + files: ["src/apps/browser/**/*.ts", "src/apps/webapp/**/*.ts"], + rules: { + "obsidianmd/no-static-styles-assignment": "off", + "obsidianmd/prefer-create-el": "off", + "obsidianmd/prefer-active-doc": "off", + }, + } +); diff --git a/eslint.config.common.mjs b/eslint.config.common.mjs index 546afcec..bdecfb05 100644 --- a/eslint.config.common.mjs +++ b/eslint.config.common.mjs @@ -91,8 +91,8 @@ export const baseRules = { */ export const obsidianRules = { // -- Obsidian rules - // obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues. - "obsidianmd/no-unsupported-api": warnWhileDev, + // Runtime fallbacks must use requireApiVersion guards recognised by the official rule. + "obsidianmd/no-unsupported-api": "error", // -- Plugin specific overrides "obsidianmd/rule-custom-message": "off", diff --git a/eslint.config.mjs b/eslint.config.mjs index 9bf2d358..b436c9e3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,10 +7,19 @@ import svelteParser from "svelte-eslint-parser"; import importAlias from "@dword-design/eslint-plugin-import-alias"; import { baseRules, ImportAliasRules, obsidianRules } from "./eslint.config.common.mjs"; const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled. +const lintProjects = [ + "./tsconfig.json", + "./src/apps/browser/tsconfig.json", + "./src/apps/cli/tsconfig.json", + "./src/apps/webapp/tsconfig.json", + "./src/apps/webpeer/tsconfig.app.json", + "./src/apps/webpeer/tsconfig.node.json", +]; export default defineConfig([ globalIgnores([ // Build outputs and legacy files "**/build", + "**/dist/**", "coverage", "**/main.js", "main_org.js", @@ -22,8 +31,7 @@ export default defineConfig([ // Files from linked dependencies (those files should not exist for most people). "modules/octagonal-wheels/dist", - // Sub-projects (Exclude from root linting as they have different environments) - "src/apps", + // Sub-project tooling with its own environment "utils", // Config files and build scripts @@ -38,6 +46,9 @@ export default defineConfig([ "vitest.*", // Testing files (Simplified patterns) "test/**", + "**/test/**", + "src/apps/_test/**", + "src/apps/cli/testdeno/**", "**/*.test.ts", "**/*.unit.spec.ts", "**/test.ts", @@ -52,8 +63,8 @@ export default defineConfig([ globals: { ...globals.browser, PouchDB: "readonly" }, parser: tsParser, parserOptions: { - project: "./tsconfig.json", - rootDir: "./", + project: lintProjects, + tsconfigRootDir: import.meta.dirname, }, }, linterOptions: { @@ -73,6 +84,8 @@ export default defineConfig([ parser: svelteParser, parserOptions: { parser: tsParser, + project: lintProjects, + tsconfigRootDir: import.meta.dirname, extraFileExtensions: [".svelte"], }, }, @@ -86,4 +99,23 @@ export default defineConfig([ ...ImportAliasRules("."), }, }, + { + files: ["src/apps/**/*.ts"], + rules: { + // Platform adapters implement asynchronous contracts even when a local operation is synchronous. + "@typescript-eslint/require-await": "off", + // Keep existing application code visible without making gradual type tightening a release blocker. + "@typescript-eslint/no-base-to-string": "warn", + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + }, + }, + { + files: ["src/apps/browser/**/*.{ts,svelte}", "src/apps/webapp/**/*.ts"], + rules: { + // Browser applications use the DOM rather than Obsidian's DOM extensions. + "obsidianmd/prefer-create-el": "off", + "obsidianmd/prefer-active-doc": "off", + }, + }, ]); diff --git a/package-lock.json b/package-lock.json index 7d19d734..9dee857b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-obsidianmd": "^0.3.0", + "eslint-plugin-obsidianmd": "^0.4.1", "eslint-plugin-svelte": "^3.19.0", "events": "^3.3.0", "globals": "^14.0.0", @@ -1813,6 +1813,36 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-plugin-eslint-comments": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.7.2.tgz", + "integrity": "sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "ignore": "^7.0.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@eslint-community/eslint-plugin-eslint-comments/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -8034,12 +8064,13 @@ } }, "node_modules/eslint-plugin-obsidianmd": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz", - "integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.4.1.tgz", + "integrity": "sha512-Nv3593kVsFOS8kir/HWaJ5CR3xcyiPWEPyXst5Pib8mxRYYuLYPpJS2ALMoZXHulHyiGwoYW8AaxvLRc4rhrRg==", "dev": true, "license": "MIT", "dependencies": { + "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/config-helpers": "^0.4.2", "@eslint/js": "^9.30.1", "@eslint/json": "0.14.0", @@ -8048,7 +8079,7 @@ "@types/node": "20.12.12", "@typescript-eslint/types": "^8.33.1", "@typescript-eslint/utils": "^8.33.1", - "eslint": ">=9.0.0", + "eslint": ">=9.19.0", "eslint-plugin-depend": "1.3.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-json-schema-validator": "5.1.0", @@ -8069,7 +8100,7 @@ "peerDependencies": { "@eslint/js": "^9.30.1", "@eslint/json": "0.14.0", - "eslint": ">=9.0.0", + "eslint": ">=9.19.0", "obsidian": "1.8.7", "typescript-eslint": "^8.35.1" } diff --git a/package.json b/package.json index 6dbfced5..64ce9893 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,17 @@ "buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original", "buildDev": "node esbuild.config.mjs dev", "lint": "eslint --cache --concurrency auto src", + "lint:community": "eslint --config eslint.community.config.mjs --concurrency auto src", "svelte-check": "svelte-check --tsconfig ./tsconfig.json", "tsc-check": "tsc --noEmit", + "tsc-check:apps": "tsc --noEmit -p src/apps/browser/tsconfig.json && tsc --noEmit -p src/apps/cli/tsconfig.json && tsc --noEmit -p src/apps/webapp/tsconfig.json && tsc --noEmit -p src/apps/webpeer/tsconfig.app.json && tsc --noEmit -p src/apps/webpeer/tsconfig.node.json", "pretty:importpath": "cd utilsdeno && deno run -A ./normalise-imports.ts", "pretty:json": "prettier --config ./.prettierrc.mjs \"**/*.json\" --write --log-level error", "pretty": "npm run prettyNoWrite -- --write --log-level error", "prettyCheck": "npm run prettyNoWrite -- --check", "prettyNoWrite": "prettier --config ./.prettierrc.mjs \"**/*.js\" \"**/*.ts\" \"**/*.json\" ", "check:compatibility": "node utils/check-compatibility.js --file main.js --ios 15", - "check": "npm run tsc-check && npm run lint && npm run svelte-check && npm run check:compatibility", + "check": "npm run tsc-check && npm run tsc-check:apps && npm run lint && npm run lint:community -- --quiet && npm run svelte-check && npm run check:compatibility", "unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/", "test": "vitest run", "test:unit": "vitest run --config vitest.config.unit.ts", @@ -108,7 +110,7 @@ "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-obsidianmd": "^0.3.0", + "eslint-plugin-obsidianmd": "^0.4.1", "eslint-plugin-svelte": "^3.19.0", "events": "^3.3.0", "globals": "^14.0.0", diff --git a/src/apps/browser/tsconfig.json b/src/apps/browser/tsconfig.json new file mode 100644 index 00000000..896bf6eb --- /dev/null +++ b/src/apps/browser/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "noEmit": true, + "paths": { + "@/*": ["../../*"] + } + }, + "include": ["**/*.ts", "**/*.svelte"], + "exclude": ["**/*.unit.spec.ts"] +} diff --git a/src/apps/browser/ui/MenuItemView.svelte b/src/apps/browser/ui/MenuItemView.svelte index d7d74e48..3a77b894 100644 --- a/src/apps/browser/ui/MenuItemView.svelte +++ b/src/apps/browser/ui/MenuItemView.svelte @@ -1,5 +1,5 @@