Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ updates:
- 'vitest'
linting:
patterns:
- '@typescript-eslint/*'
- 'eslint-*'
- 'eslint'
- 'oxlint'
- 'oxlint-tsgolint'
- 'oxfmt'
- 'prettier'
- 'typescript'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- run: yarn package lint --check
- run: yarn oxfmt --check
- run: yarn tsc
- run: yarn eslint .
- run: yarn oxlint

Unit-Tests:
runs-on: ubuntu-latest
Expand Down
33 changes: 0 additions & 33 deletions eslint.config.js

This file was deleted.

164 changes: 164 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { defineConfig } from 'oxlint';

export default defineConfig({
options: {
typeAware: true,
},
plugins: ['typescript', 'unicorn', 'oxc', 'import', 'promise', 'node', 'vitest'],
categories: {
correctness: 'error',
suspicious: 'error',
pedantic: 'error',
perf: 'error',
style: 'error',
restriction: 'error',
nursery: 'error',
},
env: {
builtin: true,
es2024: true,
node: true,
},
ignorePatterns: [
'coverage/**',
'.yarn/**',
'.turbo/**',
'packages/*/dist/**',
'packages/*/node_modules/**',
'tools/*/dist/**',
'tools/*/node_modules/**',
],
rules: {
// --- Disabled: would require rewriting the codebase for no correctness gain ---

// Style preferences that fight the established codebase style
'eslint/sort-imports': 'off',
'eslint/sort-keys': 'off',
'eslint/no-magic-numbers': 'off',
'eslint/no-ternary': 'off',
'eslint/id-length': 'off',
'eslint/no-undefined': 'off',
'eslint/init-declarations': 'off',
'eslint/func-style': 'off',
'eslint/no-inline-comments': 'off',
'eslint/no-underscore-dangle': 'off',
'eslint/max-lines': 'off',
'eslint/max-lines-per-function': 'off',
'eslint/max-statements': 'off',
'eslint/max-params': 'off',
// Library design: Node.js API, sync entry points, named+default exports
'import/no-nodejs-modules': 'off',
'import/no-relative-parent-imports': 'off',
'import/no-named-export': 'off',
'import/no-default-export': 'off',
'import/prefer-default-export': 'off',
'import/group-exports': 'off',
'import/exports-last': 'off',
'import/max-dependencies': 'off',
// Cycles are type-only (actions import interfaces from the index barrel)
'import/no-cycle': 'off',
// Sync APIs are this library's purpose
'node/no-sync': 'off',
// Banning async/await, optional chaining and spread is not practical
'oxc/no-async-await': 'off',
'oxc/no-optional-chaining': 'off',
'oxc/no-rest-spread-properties': 'off',
// Actions are exported functions using a typed `this` context by design
'oxc/no-this-in-exported-function': 'off',
// Wrapping callback APIs legitimately needs the Promise constructor
'promise/avoid-new': 'off',
// Node Transform / chmod APIs are callback- and bit-mask-based
'promise/prefer-await-to-callbacks': 'off',
'promise/prefer-await-to-then': 'off',
'promise/no-callback-in-promise': 'off',
'promise/always-return': 'off',
'node/callback-return': 'off',
'eslint/no-bitwise': 'off',
// ReadonlyDeep-style parameters would require rewriting every signature
'typescript/prefer-readonly-parameter-types': 'off',
// vinyl's API uses `null` contents
'unicorn/no-null': 'off',
// Interfaces are required for the intentional declaration merging in
// mem-fs-editor's index.ts, so no interface-vs-type enforcement
'typescript/consistent-type-definitions': 'off',
// Autofix capitalizes continuation sentences mid-comment
'eslint/capitalized-comments': 'off',
// Wrapping cached promises in async changes promise identity
'typescript/promise-function-async': 'off',
// Codebase style is inline `type` specifiers; tsc elides type-only
// imports, and forcing top-level `import type` creates duplicate imports
'typescript/no-import-type-side-effects': 'off',
// Overlaps with typescript/consistent-type-imports (inline fixStyle)
'import/consistent-type-specifier-style': 'off',
// Test style: hooks, explicit vitest imports and plain assertions are fine
'vitest/prefer-expect-assertions': 'off',
'vitest/require-test-timeout': 'off',
'vitest/no-hooks': 'off',
'vitest/no-importing-vitest-globals': 'off',
'vitest/require-to-throw-message': 'off',
'vitest/no-conditional-in-test': 'off',
'vitest/no-conditional-expect': 'off',
'vitest/prefer-to-be-truthy': 'off',
'vitest/prefer-to-be-falsy': 'off',
'vitest/prefer-strict-boolean-matchers': 'off',
// Autofix strengthens assertion semantics (broke a commit spec)
'vitest/prefer-called-with': 'off',
'vitest/prefer-called-exactly-once-with': 'off',
'vitest/prefer-called-times': 'off',
'vitest/prefer-strict-equal': 'off',
// Store/editor classes do not use explicit member accessibility
'typescript/explicit-member-accessibility': 'off',

// --- Configured to match the established style ---

// One declaration per statement (XO-compatible direction)
'eslint/one-var': ['error', 'never'],
'eslint/no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
// `== null` / `!= null` is the one allowed loose equality: it matches both
// `null` and `undefined` without distinguishing them.
'eslint/eqeqeq': ['error', 'always', { null: 'ignore' }],
'eslint/no-eq-null': 'off',
// Mixed imports use inline `type` specifiers; all-type imports use
// top-level `import type` (enforced by no-import-type-side-effects)
'typescript/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
// Exported functions need return types; inline callbacks don't
'typescript/explicit-function-return-type': [
'error',
{ allowExpressions: true, allowTypedFunctionExpressions: true },
],
// `_`-prefixed names mark intentionally unused variables
'typescript/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
// Tests live in `.spec.ts` and `.test.ts` files
'vitest/consistent-test-filename': [
'error',
{ pattern: String.raw`.*\.(test|spec)\.ts` },
],
},
overrides: [
{
// Plain Node scripts: legitimate console output, no type information
files: ['scripts/**/*.mjs'],
rules: {
'eslint/no-console': 'off',
// Not a test file; the vitest plugin matches too broadly here
'vitest/require-hook': 'off',
'typescript/no-unsafe-argument': 'off',
'typescript/no-unsafe-assignment': 'off',
'typescript/no-unsafe-call': 'off',
'typescript/no-unsafe-member-access': 'off',
'typescript/no-unsafe-return': 'off',
'typescript/strict-boolean-expressions': 'off',
},
},
],
});
14 changes: 6 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"scripts": {
"postinstall": "git config --local include.path ../.gitconfig || true",
"prepublishOnly": "yarn tsc",
"pretest": "package lint --check && eslint . && oxfmt --check && yarn tsc",
"pretest": "package lint --check && oxlint && oxfmt --check && yarn tsc",
"test": "vitest run --coverage",
"tsc": "turbo tsc && tsc -p tsconfig.json"
},
Expand All @@ -27,22 +27,20 @@
"@types/node": ">=18",
"@types/vinyl": "^2.0.12",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^10.0.2",
"eslint-config-prettier": "^10.1.8",
"eslint-config-xo": "^0.50.0",
"nano-staged": "^1.0.2",
"oxfmt": "^0.62.0",
"oxlint": "^1.78.0",
"oxlint-tsgolint": "^7.0.2001",
"turbo": "^2.9.4",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0",
"typescript": "^7.0.2",
"vitest": "^4.0.18"
},
"nano-staged": {
"*": "yarn oxfmt --no-error-on-unmatched-pattern",
"*.m?[jt]sx?": "yarn eslint --fix"
"*.m?[jt]sx?": "yarn oxlint --fix"
},
"engines": {
"node": ">=18.0.0"
},
"packageManager": "yarn@4.15.0+sha512.07ec708ac11e2eaa4ea2b04cfbb272812f7e74a753f1595eaef4486c663a98306a30cca3e6fc40f7a0b168dcfb3a2490b6a5e0501e20fb69cc36f563dd161c53"
"packageManager": "yarn@4.18.0+sha512.fcb8716fe7cd0eece141ffc18b92193a9df9204c1ba83189c288835223fc0bbe64af473bab0d5e9927a7daeb5caf2bb07eb2787cc9338ca040ea125f2a1f2f7e"
}
4 changes: 2 additions & 2 deletions packages/mem-fs-editor/src/actions/append-tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default function appendTpl(
data?: ejs.Data,
options?: AppendTplOptions,
]
) {
if (options?.transformOptions?.async) {
): void {
if (options?.transformOptions?.async === true) {
throw new Error('Async EJS rendering is not supported');
}

Expand Down
4 changes: 2 additions & 2 deletions packages/mem-fs-editor/src/actions/append.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { EOL } from 'os';
import { EOL } from 'node:os';
import type { MemFsEditor } from '../index.ts';

export default function append(
this: MemFsEditor,
to: string,
contents: string | Buffer,
options?: { create?: boolean; trimEnd?: boolean; separator?: string },
) {
): void {
const opts = {
create: false,
trimEnd: true,
Expand Down
16 changes: 8 additions & 8 deletions packages/mem-fs-editor/src/actions/commit-file-async.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs/promises';
import path from 'path';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
clearFileState,
isFileStateModified,
Expand All @@ -10,17 +10,18 @@ import {
import type { MemFsEditorFile } from '../index.ts';

function hasErrorCode(error: unknown): error is { code: string } {
return typeof error === 'object' && error !== null && 'code' in error;
return typeof error === 'object' && error != null && 'code' in error;
}

async function write(file: MemFsEditorFile) {
async function write(file: MemFsEditorFile): Promise<void> {
if (!file.contents) {
throw new Error(`${file.path} cannot write an empty file`);
}

const dir = path.dirname(file.path);
try {
if (!(await fs.stat(dir)).isDirectory()) {
const dirStat = await fs.stat(dir);
if (!dirStat.isDirectory()) {
throw new Error(`${dir} is not a directory`);
}
} catch (error) {
Expand All @@ -34,16 +35,15 @@ async function write(file: MemFsEditorFile) {
const newMode = file.stat?.mode;
await fs.writeFile(file.path, file.contents, { mode: newMode });

if (newMode !== undefined) {
if (newMode != null) {
const { mode: existingMode } = await fs.stat(file.path);
// eslint-disable-next-line no-bitwise
if ((existingMode & 0o777) !== (newMode & 0o777)) {
await fs.chmod(file.path, newMode);
}
}
}

export default async function commitFileAsync(file: MemFsEditorFile) {
export default async function commitFileAsync(file: MemFsEditorFile): Promise<void> {
if (isFileStateModified(file)) {
setCommittedFile(file);
await write(file);
Expand Down
11 changes: 7 additions & 4 deletions packages/mem-fs-editor/src/actions/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ async function commit<EditorFile extends MemFsEditorFile>(
options?: PipelineOptions<EditorFile> | FileTransform<EditorFile>,
...transforms: FileTransform<EditorFile>[]
): Promise<void> {
let pipelineOptions: PipelineOptions<EditorFile> | undefined;
let pipelineTransforms = transforms;
if (isFileTransform<EditorFile>(options)) {
transforms = [options, ...transforms];
options = undefined;
pipelineTransforms = [options, ...transforms];
} else {
pipelineOptions = options;
}

await this.store.pipeline(
{ filter: isFilePending, ...options },
...transforms,
{ filter: isFilePending, ...pipelineOptions },
...pipelineTransforms,
createCommitTransform(),
);
}
Expand Down
Loading