diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3fb669e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + +[*.{js,json}] +indent_style = tab +indent_size = tab +tab_width = 4 +trim_trailing_whitespace = true diff --git a/.github/workflows/eslint.yaml b/.github/workflows/eslint.yaml new file mode 100644 index 0000000..7044d24 --- /dev/null +++ b/.github/workflows/eslint.yaml @@ -0,0 +1,25 @@ +name: ESLint + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + timeout-minutes: 5 + run: npm run format:check diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index a0f78c6..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "useTabs": true, - "tabWidth": 1, - "semi": true, - "singleQuote": true, - "trailingComma": "none", - "bracketSpacing": false, - "arrowParens": "avoid" -} diff --git a/Syntax.js b/Syntax.js index 7d41375..b3a1b5d 100644 --- a/Syntax.js +++ b/Syntax.js @@ -237,12 +237,12 @@ export class Syntax { } catch (error) { throw new LanguageLoadError(name, path, {cause: error}); } - + // If the module exports a register function, call it with this instance if (typeof module.default === 'function') { module.default(this); } - + // After calling register, aliases have been registered. Re-resolve the name: let resolvedName = this.#aliases[name] || name; return loader.get(resolvedName); diff --git a/Syntax/CodeElement.js b/Syntax/CodeElement.js index 57c5176..4c8de1e 100644 --- a/Syntax/CodeElement.js +++ b/Syntax/CodeElement.js @@ -83,7 +83,7 @@ export class CodeElement extends HTMLElement { constructor() { super(); - + /** * A promise that resolves when the current highlighting attempt completes. * Check `highlighted` before using line measurement APIs. @@ -150,26 +150,26 @@ export class CodeElement extends HTMLElement { */ getLineBoundingClientRect(lineNumber) { if (!this.#shadow) return null; - + const code = this.#shadow.querySelector('code'); if (!code) return null; - + const lines = code.children; if (lineNumber < 1 || lineNumber > lines.length) return null; - + return lines[lineNumber - 1].getBoundingClientRect(); } - + /** * Get the total number of rendered lines. * @returns {number} The line count, or 0 if not yet rendered. */ get lineCount() { if (!this.#shadow) return 0; - + const code = this.#shadow.querySelector('code'); if (!code) return 0; - + return code.children.length; } @@ -180,7 +180,7 @@ export class CodeElement extends HTMLElement { get highlighted() { return this.#highlighted; } - + connectedCallback() { // Detect if we're inside a
 element and set wrap attribute
 		if (this.parentElement?.tagName === 'PRE') {
diff --git a/Syntax/Rule.js b/Syntax/Rule.js
index 65619b6..35dd553 100644
--- a/Syntax/Rule.js
+++ b/Syntax/Rule.js
@@ -134,14 +134,14 @@ export class Rule {
 	 * Create a conditional matcher that selects a rule based on another capture group.
 	 * Tests a condition capture group against patterns to determine which rule to apply
 	 * to a content capture group.
-	 * 
+	 *
 	 * @param {number} conditionIndex - Capture group index to test against patterns
 	 * @param {number} contentIndex - Capture group index containing content to match
 	 * @param {Array<{pattern?: RegExp, ...rule}>} conditions - Array of condition objects. Each can have:
 	 *   - pattern: RegExp to test against the condition group (optional - if omitted, acts as fallback)
 	 *   - Any rule properties (language, type, etc.) to apply when pattern matches
 	 * @returns {Function} A matches function for use in language rules
-	 * 
+	 *
 	 * @example
 	 * // Script tags with type-based language selection
 	 * language.push({
@@ -152,7 +152,7 @@ export class Rule {
 	 *     {language: 'javascript'} // Fallback for no type or unknown types
 	 *   ])
 	 * });
-	 * 
+	 *
 	 * @example
 	 * // Code fence with language specifier
 	 * language.push({
@@ -163,7 +163,7 @@ export class Rule {
 	 *     {language: 'plaintext'} // Fallback
 	 *   ])
 	 * });
-	 * 
+	 *
 	 * @example
 	 * // Conditional type based on prefix
 	 * language.push({
@@ -201,7 +201,7 @@ export class Rule {
 
 			// Build syntax tree or create match based on rule properties
 			const offset = match.index + match[0].indexOf(content);
-			
+
 			if (ruleProps.language) {
 				return [
 					await Language.buildTree(
diff --git a/bin/syntax-ast.js b/bin/syntax-ast.js
index 9a55889..e89bcec 100755
--- a/bin/syntax-ast.js
+++ b/bin/syntax-ast.js
@@ -4,26 +4,26 @@ import Syntax from '../Syntax.js';
 
 async function main() {
 	const args = process.argv.slice(2);
-	
+
 	if (args.length < 2) {
 		console.error('Usage: syntax-ast  ');
 		console.error('Example: syntax-ast javascript "const x = 1;"');
 		process.exit(1);
 	}
-	
+
 	const [languageName, code] = args;
-	
+
 	const syntax = new Syntax();
-	
+
 	try {
 		const language = await syntax.getLanguage(languageName);
 		const matches = await language.getMatches(syntax, code);
-		
+
 		console.log('Language:', languageName);
 		console.log('Code:', JSON.stringify(code));
 		console.log('\nMatches:', matches.length);
 		console.log('─'.repeat(80));
-		
+
 		for (const match of matches) {
 			const text = code.substring(match.offset, match.offset + match.length);
 			console.log(`[${match.type}] @${match.offset}..${match.offset + match.length} (${match.length} chars)`);
@@ -32,7 +32,7 @@ async function main() {
 				console.log(`  Children: ${match.children.length}`);
 			}
 		}
-		
+
 	} catch (error) {
 		console.error('Error:', error.message);
 		process.exit(1);
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..647141c
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,44 @@
+import stylistic from '@stylistic/eslint-plugin';
+
+export default [
+	{
+		plugins: {
+			'@stylistic': stylistic
+		},
+		rules: {
+			'@stylistic/array-bracket-spacing': ['error', 'never'],
+			'@stylistic/arrow-parens': ['error', 'as-needed'],
+			'@stylistic/arrow-spacing': 'error',
+			'@stylistic/block-spacing': ['error', 'always'],
+			'@stylistic/brace-style': [
+				'error',
+				'1tbs',
+				{allowSingleLine: true}
+			],
+			'@stylistic/comma-dangle': ['error', 'never'],
+			'@stylistic/comma-spacing': 'error',
+			'@stylistic/computed-property-spacing': ['error', 'never'],
+			'@stylistic/eol-last': ['error', 'always'],
+			'@stylistic/function-call-spacing': ['error', 'never'],
+			'@stylistic/indent': ['error', 'tab', {SwitchCase: 1}],
+			'@stylistic/key-spacing': 'error',
+			'@stylistic/keyword-spacing': 'error',
+			'@stylistic/no-mixed-spaces-and-tabs': 'error',
+			'@stylistic/no-trailing-spaces': 'error',
+			'@stylistic/object-curly-spacing': ['error', 'never'],
+			'@stylistic/quotes': ['error', 'single', {avoidEscape: true}],
+			'@stylistic/rest-spread-spacing': ['error', 'never'],
+			'@stylistic/semi': ['error', 'always'],
+			'@stylistic/semi-spacing': 'error',
+			'@stylistic/space-before-blocks': 'error',
+			'@stylistic/space-before-function-paren': [
+				'error',
+				{anonymous: 'always', asyncArrow: 'always', named: 'never'}
+			],
+			'@stylistic/space-in-parens': ['error', 'never'],
+			'@stylistic/space-infix-ops': 'error',
+			'@stylistic/space-unary-ops': 'error',
+			'@stylistic/template-curly-spacing': ['error', 'never']
+		}
+	}
+];
diff --git a/package-lock.json b/package-lock.json
index bd4d0a2..94b779a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,9 @@
 			"version": "0.4.0",
 			"license": "MIT",
 			"devDependencies": {
-				"jsdom": "^25.0.0",
-				"prettier": "^3.6.2"
+				"@stylistic/eslint-plugin": "^5.10.0",
+				"eslint": "^10.8.1",
+				"jsdom": "^25.0.0"
 			}
 		},
 		"node_modules/@asamuzakjp/css-color": {
@@ -115,7 +116,6 @@
 				}
 			],
 			"license": "MIT",
-			"peer": true,
 			"engines": {
 				"node": ">=18"
 			},
@@ -139,11 +139,262 @@
 				}
 			],
 			"license": "MIT",
-			"peer": true,
 			"engines": {
 				"node": ">=18"
 			}
 		},
+		"node_modules/@eslint-community/eslint-utils": {
+			"version": "4.10.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+			"integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"eslint-visitor-keys": "^3.4.3"
+			},
+			"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"
+			}
+		},
+		"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+			"version": "3.4.3",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+			"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/@eslint-community/regexpp": {
+			"version": "4.12.2",
+			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+			"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+			}
+		},
+		"node_modules/@eslint/config-array": {
+			"version": "0.23.5",
+			"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+			"integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@eslint/object-schema": "^3.0.5",
+				"debug": "^4.3.1",
+				"minimatch": "^10.2.4"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@eslint/config-helpers": {
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
+			"integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@eslint/core": "^1.2.1"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@eslint/core": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+			"integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@types/json-schema": "^7.0.15"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@eslint/object-schema": {
+			"version": "3.0.5",
+			"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+			"integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@eslint/plugin-kit": {
+			"version": "0.7.2",
+			"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
+			"integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@eslint/core": "^1.2.1",
+				"levn": "^0.4.1"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@humanfs/core": {
+			"version": "0.19.2",
+			"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+			"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@humanfs/types": "^0.15.0"
+			},
+			"engines": {
+				"node": ">=18.18.0"
+			}
+		},
+		"node_modules/@humanfs/node": {
+			"version": "0.16.8",
+			"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+			"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"dependencies": {
+				"@humanfs/core": "^0.19.2",
+				"@humanfs/types": "^0.15.0",
+				"@humanwhocodes/retry": "^0.4.0"
+			},
+			"engines": {
+				"node": ">=18.18.0"
+			}
+		},
+		"node_modules/@humanfs/types": {
+			"version": "0.15.0",
+			"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+			"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": ">=18.18.0"
+			}
+		},
+		"node_modules/@humanwhocodes/module-importer": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+			"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": ">=12.22"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/nzakas"
+			}
+		},
+		"node_modules/@humanwhocodes/retry": {
+			"version": "0.4.3",
+			"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+			"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": ">=18.18"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/nzakas"
+			}
+		},
+		"node_modules/@stylistic/eslint-plugin": {
+			"version": "5.10.0",
+			"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz",
+			"integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"@eslint-community/eslint-utils": "^4.9.1",
+				"@typescript-eslint/types": "^8.56.0",
+				"eslint-visitor-keys": "^4.2.1",
+				"espree": "^10.4.0",
+				"estraverse": "^5.3.0",
+				"picomatch": "^4.0.3"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"peerDependencies": {
+				"eslint": "^9.0.0 || ^10.0.0"
+			}
+		},
+		"node_modules/@types/esrecurse": {
+			"version": "4.3.1",
+			"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+			"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/@types/estree": {
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+			"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/@types/json-schema": {
+			"version": "7.0.15",
+			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+			"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/@typescript-eslint/types": {
+			"version": "8.67.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz",
+			"integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
+			}
+		},
+		"node_modules/acorn": {
+			"version": "8.18.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+			"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
+			"dev": true,
+			"license": "MIT",
+			"bin": {
+				"acorn": "bin/acorn"
+			},
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/acorn-jsx": {
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+			"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+			"dev": true,
+			"license": "MIT",
+			"peerDependencies": {
+				"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+			}
+		},
 		"node_modules/agent-base": {
 			"version": "7.1.4",
 			"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -154,6 +405,23 @@
 				"node": ">= 14"
 			}
 		},
+		"node_modules/ajv": {
+			"version": "6.15.0",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+			"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/epoberezkin"
+			}
+		},
 		"node_modules/asynckit": {
 			"version": "0.4.0",
 			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -161,6 +429,29 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/balanced-match": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+			"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": "18 || 20 || >=22"
+			}
+		},
+		"node_modules/brace-expansion": {
+			"version": "5.0.9",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+			"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"balanced-match": "^4.0.2"
+			},
+			"engines": {
+				"node": "20 || >=22"
+			}
+		},
 		"node_modules/call-bind-apply-helpers": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -188,6 +479,21 @@
 				"node": ">= 0.8"
 			}
 		},
+		"node_modules/cross-spawn": {
+			"version": "7.0.6",
+			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+			"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"path-key": "^3.1.0",
+				"shebang-command": "^2.0.0",
+				"which": "^2.0.1"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
 		"node_modules/cssstyle": {
 			"version": "4.6.0",
 			"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
@@ -248,6 +554,13 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/deep-is": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+			"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/delayed-stream": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -335,6 +648,277 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/escape-string-regexp": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+			"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint": {
+			"version": "10.8.1",
+			"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz",
+			"integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==",
+			"dev": true,
+			"license": "MIT",
+			"workspaces": [
+				"packages/*"
+			],
+			"dependencies": {
+				"@eslint-community/eslint-utils": "^4.8.0",
+				"@eslint-community/regexpp": "^4.12.2",
+				"@eslint/config-array": "^0.23.5",
+				"@eslint/config-helpers": "^0.7.0",
+				"@eslint/core": "^1.2.1",
+				"@eslint/plugin-kit": "^0.7.2",
+				"@humanfs/node": "^0.16.6",
+				"@humanwhocodes/module-importer": "^1.0.1",
+				"@humanwhocodes/retry": "^0.4.2",
+				"@types/estree": "^1.0.6",
+				"ajv": "^6.14.0",
+				"cross-spawn": "^7.0.6",
+				"debug": "^4.3.2",
+				"escape-string-regexp": "^4.0.0",
+				"eslint-scope": "^9.1.2",
+				"eslint-visitor-keys": "^5.0.1",
+				"espree": "^11.2.0",
+				"esquery": "^1.7.0",
+				"esutils": "^2.0.2",
+				"fast-deep-equal": "^3.1.3",
+				"file-entry-cache": "^8.0.0",
+				"find-up": "^5.0.0",
+				"glob-parent": "^6.0.2",
+				"ignore": "^5.2.0",
+				"imurmurhash": "^0.1.4",
+				"is-glob": "^4.0.0",
+				"json-stable-stringify-without-jsonify": "^1.0.1",
+				"minimatch": "^10.2.5",
+				"natural-compare": "^1.4.0",
+				"optionator": "^0.9.3"
+			},
+			"bin": {
+				"eslint": "bin/eslint.js"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			},
+			"funding": {
+				"url": "https://eslint.org/donate"
+			},
+			"peerDependencies": {
+				"jiti": "*"
+			},
+			"peerDependenciesMeta": {
+				"jiti": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/eslint-scope": {
+			"version": "9.1.2",
+			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+			"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"dependencies": {
+				"@types/esrecurse": "^4.3.1",
+				"@types/estree": "^1.0.8",
+				"esrecurse": "^4.3.0",
+				"estraverse": "^5.2.0"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-visitor-keys": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint/node_modules/eslint-visitor-keys": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+			"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+			"dev": true,
+			"license": "Apache-2.0",
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint/node_modules/espree": {
+			"version": "11.2.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+			"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"dependencies": {
+				"acorn": "^8.16.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^5.0.1"
+			},
+			"engines": {
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/espree": {
+			"version": "10.4.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+			"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"dependencies": {
+				"acorn": "^8.15.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^4.2.1"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/esquery": {
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+			"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+			"dev": true,
+			"license": "BSD-3-Clause",
+			"dependencies": {
+				"estraverse": "^5.1.0"
+			},
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/esrecurse": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+			"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"dependencies": {
+				"estraverse": "^5.2.0"
+			},
+			"engines": {
+				"node": ">=4.0"
+			}
+		},
+		"node_modules/estraverse": {
+			"version": "5.3.0",
+			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+			"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"engines": {
+				"node": ">=4.0"
+			}
+		},
+		"node_modules/esutils": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/fast-levenshtein": {
+			"version": "2.0.6",
+			"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+			"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/file-entry-cache": {
+			"version": "8.0.0",
+			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+			"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"flat-cache": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=16.0.0"
+			}
+		},
+		"node_modules/find-up": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+			"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"locate-path": "^6.0.0",
+				"path-exists": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/flat-cache": {
+			"version": "4.0.1",
+			"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+			"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"flatted": "^3.2.9",
+				"keyv": "^4.5.4"
+			},
+			"engines": {
+				"node": ">=16"
+			}
+		},
+		"node_modules/flatted": {
+			"version": "3.4.4",
+			"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
+			"integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
+			"dev": true,
+			"license": "ISC"
+		},
 		"node_modules/form-data": {
 			"version": "4.0.4",
 			"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
@@ -401,6 +985,19 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/glob-parent": {
+			"version": "6.0.2",
+			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+			"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+			"dev": true,
+			"license": "ISC",
+			"dependencies": {
+				"is-glob": "^4.0.3"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
 		"node_modules/gopd": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -510,6 +1107,49 @@
 				"node": ">=0.10.0"
 			}
 		},
+		"node_modules/ignore": {
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+			"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">= 4"
+			}
+		},
+		"node_modules/imurmurhash": {
+			"version": "0.1.4",
+			"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+			"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=0.8.19"
+			}
+		},
+		"node_modules/is-extglob": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+			"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/is-glob": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+			"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"is-extglob": "^2.1.1"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
 		"node_modules/is-potential-custom-element-name": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -517,6 +1157,13 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/isexe": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+			"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+			"dev": true,
+			"license": "ISC"
+		},
 		"node_modules/jsdom": {
 			"version": "25.0.1",
 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
@@ -558,6 +1205,67 @@
 				}
 			}
 		},
+		"node_modules/json-buffer": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+			"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/json-stable-stringify-without-jsonify": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+			"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+			"dev": true,
+			"license": "MIT"
+		},
+		"node_modules/keyv": {
+			"version": "4.5.4",
+			"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+			"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"json-buffer": "3.0.1"
+			}
+		},
+		"node_modules/levn": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+			"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"prelude-ls": "^1.2.1",
+				"type-check": "~0.4.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/locate-path": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+			"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"p-locate": "^5.0.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
 		"node_modules/lru-cache": {
 			"version": "10.4.3",
 			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -598,6 +1306,22 @@
 				"node": ">= 0.6"
 			}
 		},
+		"node_modules/minimatch": {
+			"version": "10.2.6",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+			"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+			"dev": true,
+			"license": "BlueOak-1.0.0",
+			"dependencies": {
+				"brace-expansion": "^5.0.8"
+			},
+			"engines": {
+				"node": "18 || 20 || >=22"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
 		"node_modules/ms": {
 			"version": "2.1.3",
 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -605,6 +1329,13 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/natural-compare": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+			"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+			"dev": true,
+			"license": "MIT"
+		},
 		"node_modules/nwsapi": {
 			"version": "2.2.22",
 			"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz",
@@ -612,6 +1343,56 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/optionator": {
+			"version": "0.9.4",
+			"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+			"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"deep-is": "^0.1.3",
+				"fast-levenshtein": "^2.0.6",
+				"levn": "^0.4.1",
+				"prelude-ls": "^1.2.1",
+				"type-check": "^0.4.0",
+				"word-wrap": "^1.2.5"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/p-limit": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+			"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"yocto-queue": "^0.1.0"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/p-locate": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+			"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"p-limit": "^3.0.2"
+			},
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
 		"node_modules/parse5": {
 			"version": "7.3.0",
 			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -625,20 +1406,47 @@
 				"url": "https://github.com/inikulin/parse5?sponsor=1"
 			}
 		},
-		"node_modules/prettier": {
-			"version": "3.6.2",
-			"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
-			"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+		"node_modules/path-exists": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+			"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/path-key": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+			"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
 			"dev": true,
 			"license": "MIT",
-			"bin": {
-				"prettier": "bin/prettier.cjs"
-			},
 			"engines": {
-				"node": ">=14"
+				"node": ">=8"
+			}
+		},
+		"node_modules/picomatch": {
+			"version": "4.0.5",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+			"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=12"
 			},
 			"funding": {
-				"url": "https://github.com/prettier/prettier?sponsor=1"
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
+		"node_modules/prelude-ls": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+			"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">= 0.8.0"
 			}
 		},
 		"node_modules/punycode": {
@@ -678,6 +1486,29 @@
 				"node": ">=v12.22.7"
 			}
 		},
+		"node_modules/shebang-command": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+			"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"shebang-regex": "^3.0.0"
+			},
+			"engines": {
+				"node": ">=8"
+			}
+		},
+		"node_modules/shebang-regex": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+			"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=8"
+			}
+		},
 		"node_modules/symbol-tree": {
 			"version": "3.2.4",
 			"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -731,6 +1562,29 @@
 				"node": ">=18"
 			}
 		},
+		"node_modules/type-check": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+			"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"prelude-ls": "^1.2.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/uri-js": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+			"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+			"dev": true,
+			"license": "BSD-2-Clause",
+			"dependencies": {
+				"punycode": "^2.1.0"
+			}
+		},
 		"node_modules/w3c-xmlserializer": {
 			"version": "5.0.0",
 			"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -791,6 +1645,32 @@
 				"node": ">=18"
 			}
 		},
+		"node_modules/which": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+			"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+			"dev": true,
+			"license": "ISC",
+			"dependencies": {
+				"isexe": "^2.0.0"
+			},
+			"bin": {
+				"node-which": "bin/node-which"
+			},
+			"engines": {
+				"node": ">= 8"
+			}
+		},
+		"node_modules/word-wrap": {
+			"version": "1.2.5",
+			"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+			"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
 		"node_modules/ws": {
 			"version": "8.18.3",
 			"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
@@ -829,6 +1709,19 @@
 			"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
 			"dev": true,
 			"license": "MIT"
+		},
+		"node_modules/yocto-queue": {
+			"version": "0.1.0",
+			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+			"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=10"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
 		}
 	}
 }
diff --git a/package.json b/package.json
index 42c597b..68a0444 100644
--- a/package.json
+++ b/package.json
@@ -20,8 +20,8 @@
 	],
 	"scripts": {
 		"test": "node --test 'test/**/*.js'",
-		"format": "prettier --write '**/*.{js,json,md}'",
-		"format:check": "prettier --check '**/*.{js,json,md}'"
+		"format": "eslint . --fix",
+		"format:check": "eslint ."
 	},
 	"keywords": [
 		"syntax",
@@ -37,7 +37,8 @@
 		"url": "https://github.com/socketry/syntax-js.git"
 	},
 	"devDependencies": {
-		"jsdom": "^25.0.0",
-		"prettier": "^3.6.2"
+		"@stylistic/eslint-plugin": "^5.10.0",
+		"eslint": "^10.8.1",
+		"jsdom": "^25.0.0"
 	}
 }
diff --git a/test/Syntax/CodeElement.js b/test/Syntax/CodeElement.js
index 7c3efef..1ea90c3 100644
--- a/test/Syntax/CodeElement.js
+++ b/test/Syntax/CodeElement.js
@@ -202,7 +202,7 @@ test('upgradeAll handles 
 blocks without double-nesting', async () =>
 		'const x = 1;',
 		'Code content should be preserved'
 	);
-	
+
 	// Verify wrap attribute is set (because it's inside 
)
 	// Note: This happens in connectedCallback, which may not fire in JSDOM
 	// We'll just verify structure for now
@@ -346,21 +346,21 @@ test('syntax-code behaves semantically like  when inline', async () => {
 	`;
 
 	const element = document.querySelector('syntax-code');
-	
+
 	// Wait for rendering to complete
 	await new Promise(resolve => setTimeout(resolve, 100));
 
 	// Verify it's inline (like ) by checking the shadow DOM structure
 	const shadowRoot = element.shadowRoot;
 	assert.ok(shadowRoot, 'Shadow root should exist');
-	
+
 	// Should contain a  element, not wrapped in 
 	const codeElement = shadowRoot.querySelector('code');
 	assert.ok(codeElement, 'Shadow DOM should contain  element');
-	
+
 	const preElement = shadowRoot.querySelector('pre');
 	assert.equal(preElement, null, 'Shadow DOM should NOT contain 
 wrapper for inline usage');
-	
+
 	// The code element should be a direct child of shadow root
 	assert.ok(
 		Array.from(shadowRoot.children).includes(codeElement),
@@ -379,18 +379,18 @@ test('syntax-code behaves as block when inside 
', async () => {
 	`;
 
 	const element = document.querySelector('syntax-code');
-	
+
 	// Wait for rendering to complete
 	await new Promise(resolve => setTimeout(resolve, 100));
 
 	// Check that wrap attribute was set (because it's inside 
)
 	assert.ok(element.hasAttribute('wrap'), 'wrap attribute should be set when inside 
');
-	
+
 	// After rendering, light DOM is cleared, so check shadow DOM
 	const shadowRoot = element.shadowRoot;
 	const codeElement = shadowRoot.querySelector('code');
 	assert.ok(codeElement, 'Shadow DOM should contain  element');
-	
+
 	// The  should be a direct child of shadow root (no 
 wrapper needed inside)
 	assert.ok(
 		Array.from(shadowRoot.children).includes(codeElement),
diff --git a/test/Syntax/Language/markdown.js b/test/Syntax/Language/markdown.js
index 3c09c91..22260f9 100644
--- a/test/Syntax/Language/markdown.js
+++ b/test/Syntax/Language/markdown.js
@@ -31,10 +31,10 @@ test('Markdown can match headers', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '# Heading 1';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'heading', '# Heading 1');
 });
 
@@ -42,10 +42,10 @@ test('Markdown can match bold text', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '**bold text**';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'strong', '**bold text**');
 });
 
@@ -53,10 +53,10 @@ test('Markdown can match italic text', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '*italic text*';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'emphasis', '*italic text*');
 });
 
@@ -64,10 +64,10 @@ test('Markdown can match inline code', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '`code here`';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'code', '`code here`');
 });
 
@@ -75,10 +75,10 @@ test('Markdown can match code blocks', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '```javascript\nconst x = 1;\n```';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'code', '```javascript\nconst x = 1;\n```');
 });
 
@@ -86,10 +86,10 @@ test('Markdown can match links', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '[text](http://example.com)';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	// Should match the link text and URL as separate tokens
 	assertToken(code, matches, 'string', 'text');
 	assertToken(code, matches, 'link', 'http://example.com');
@@ -99,10 +99,10 @@ test('Markdown can match images', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '![alt text](image.png)';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'link', '![alt text](image.png)');
 });
 
@@ -110,10 +110,10 @@ test('Markdown can match blockquotes', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '> This is a quote';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'quote', '> This is a quote');
 });
 
@@ -121,10 +121,10 @@ test('Markdown can match unordered lists', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '- List item';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'list-marker', '- ');
 });
 
@@ -132,10 +132,10 @@ test('Markdown can match ordered lists', async () => {
 	const syntax = new Syntax();
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
-	
+
 	const code = '1. List item';
 	const matches = await language.getMatches(syntax, code);
-	
+
 	assertToken(code, matches, 'list-marker', '1. ');
 });
 
@@ -144,7 +144,7 @@ test('Markdown: fenced block followed by inline code does not merge', async () =
 	registerMarkdown(syntax);
 	const language = await syntax.getLanguage('markdown');
 
-	const code = "```\nfoo\n```\n\n`project`";
+	const code = '```\nfoo\n```\n\n`project`';
 	const matches = await language.getMatches(syntax, code);
 
 	// Expect a fenced code block token:
diff --git a/update-examples.js b/update-examples.js
index 958d94b..6c21c14 100755
--- a/update-examples.js
+++ b/update-examples.js
@@ -1,7 +1,7 @@
 #!/usr/bin/env node
 
-import { readFileSync, writeFileSync, readdirSync } from 'fs';
-import { join } from 'path';
+import {readFileSync, writeFileSync, readdirSync} from 'fs';
+import {join} from 'path';
 
 const examplesDir = './examples';
 const excludeFiles = new Set([
@@ -56,7 +56,7 @@ const languageNames = {
 };
 
 // Process each file
-const files = readdirSync(examplesDir).filter(f => 
+const files = readdirSync(examplesDir).filter(f =>
 	f.endsWith('.html') && !excludeFiles.has(f)
 );
 
@@ -65,18 +65,18 @@ console.log(`Processing ${files.length} files...`);
 files.forEach(file => {
 	const filePath = join(examplesDir, file);
 	let content = readFileSync(filePath, 'utf8');
-	
+
 	const baseName = file.replace('.html', '');
 	const langName = languageNames[baseName] || baseName;
-	
+
 	// Check if already updated
 	if (content.includes('examples.css')) {
 		console.log(`✓ ${file} already updated`);
 		return;
 	}
-	
+
 	console.log(`Updating ${file}...`);
-	
+
 	// Replace  with inline styles
 	content = content.replace(
 		/[\s\S]*?<\/head>/,
@@ -87,7 +87,7 @@ files.forEach(file => {
 	
 `
 	);
-	
+
 	// Add header if not present
 	if (!content.includes('
')) { content = content.replace( @@ -105,7 +105,7 @@ files.forEach(file => { ` ); } - + // Wrap examples in div.example if needed if (!content.includes('class="example"')) { // Simple wrapping for code blocks @@ -127,7 +127,7 @@ files.forEach(file => { '$1\n\t\n\t\n\t