Lint with Oxlint through Vite+ instead of ESLint

Replace ESLint, typescript-eslint, eslint-plugin-node and the flat
config helpers with vite-plus, which runs Oxlint for vp lint. The rules
are carried over from eslint.config.mjs by @oxlint/migrate into the lint
section of the root vite.config.ts. The formatting rules ESLint
deprecated run as @stylistic rules through Oxlint's JS plugin support,
and disable comments naming them are updated to the @stylistic names.

@stylistic understands TypeScript syntax that the core rules skipped,
so the autofix adds missing semicolons and spaces in type literals and
aliases. Leading | and & in multiline types stay where they were.

Oxlint's node/no-sync also reports identifiers ending in Sync that are
not calls. The test helper techSync is renamed and the two other names
are ignored.

Refs #1127

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 5.5 committed 2026-10-27 22:53:13 +00:00
1 parent 3227179613
commit e94abe51c9
35 files changed
+445 -578

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ Use the detailed guides for the affected area:
- Read the relevant source and tests before changing behavior.
If documentation and implementation disagree, report the discrepancy and distinguish observed behavior from intended policy.
Update affected documentation in the same change.
- Follow nearby code and `eslint.config.mjs`: tabs, double quotes, semicolons, and `.js` extensions in relative TypeScript imports.
- Follow nearby code and the `lint` section of `vite.config.ts`: tabs, double quotes, semicolons, and `.js` extensions in relative TypeScript imports.
Import shared public APIs through `@clusterio/lib` and check Node/browser exports when changing them.
- Run commands from the root using the pinned pnpm version and the [documented Node requirements](docs/contributing.md#supported-nodejs-and-factorio-versions).
Install with `pnpm install --no-frozen-lockfile`; the lockfile is intentionally ignored.
+7 -6
View File
@@ -18,7 +18,7 @@ If there is a particular issue or concern you would like the team to comment on
- [Naming Times and Durations](#naming-times-and-durations)
- [Strings](#strings)
3. Code Style (TS / JS / Web)
- [ESLint](#eslint)
- [Oxlint](#oxlint)
- [Imports Within `lib` Package](#imports-within-lib-package)
- [Name Style](#name-style-ts)
4. Code Style (Lua)
@@ -157,14 +157,15 @@ Therefore, we selected double quotes as our standard.
## Code Style (TS / JS / Web)
### ESLint
### Oxlint
🧾 Time Immemorial
🧾 20th October 2026
> Use ESLint for style checking.
> Use Oxlint through `vp lint` for style checking, with formatting rules from `@stylistic/eslint-plugin`.
We needed to maintain a consistent style across all our files.
Therefore, we utilise the industry-standard ESLint, as it is familiar and highly configurable.
We needed to maintain a consistent style across all our files and used ESLint for it from the start.
Oxlint runs the same rules about three times faster, reads ESLint disable comments, and does not depend on the TypeScript compiler API.
The formatting rules ESLint deprecated run through `@stylistic/eslint-plugin` loaded as an Oxlint JS plugin, so the code style did not change.
### Imports Within `lib` Package
-504
View File
@@ -1,504 +0,0 @@
import {
defineConfig,
globalIgnores,
} from "eslint/config";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import node from "eslint-plugin-node";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import js from "@eslint/js";
import stylistic from "@stylistic/eslint-plugin";
import {
FlatCompat,
} from "@eslint/eslintrc";
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([{
plugins: {
"@typescript-eslint": typescriptEslint,
"@stylistic": stylistic,
node,
},
languageOptions: {
globals: {
...globals.node,
...globals.commonjs,
},
},
"rules": {
"@stylistic/indent": ["error", "tab", {
"ignoredNodes": [
"JSXFragment", // TODO: Linter crashes if this is removed
],
"ignoreComments": true,
"SwitchCase": 1, // Indent case statements one level from switch
}],
"accessor-pairs": "error",
"array-bracket-newline": "off",
"array-bracket-spacing": ["error", "never"],
"array-callback-return": "error",
"array-element-newline": "off",
"arrow-body-style": "error",
"arrow-parens": "off",
"arrow-spacing": ["error", {
"after": true,
"before": true,
}],
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": ["error", "1tbs", {
"allowSingleLine": true,
}],
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "off",
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "only-multiline",
}],
"comma-spacing": "error",
"comma-style": ["error", "last"],
"complexity": ["error", { "max": 25 }],
"computed-property-spacing": ["error", "never"],
"consistent-return": "error",
"consistent-this": "error",
"curly": "error",
"default-case": "error",
"default-case-last": "error",
"default-param-last": "off",
"dot-location": ["error", "property"],
"dot-notation": ["error", {
"allowPattern": "^[a-z]+(_[a-z]+)*$",
}],
"eol-last": "error",
"eqeqeq": "error",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": "off",
"func-style": ["error", "declaration", {
"allowArrowFunctions": true,
}],
"function-call-argument-newline": "off",
"function-paren-newline": "off",
"generator-star-spacing": ["error", {
"before": false,
"after": true,
}],
"grouped-accessor-pairs": "error",
"guard-for-in": "error",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"implicit-arrow-linebreak": "error",
"indent-legacy": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "error",
"keyword-spacing": "error",
"line-comment-position": "off",
"linebreak-style": ["error", "unix"],
"lines-around-comment": "off",
"lines-around-directive": "off",
"lines-between-class-members": ["error", "always", {
"exceptAfterSingleLine": true,
}],
"max-classes-per-file": "off",
"max-depth": "error",
"max-len": ["error", {
"code": 120,
}],
"max-lines": "off",
"max-lines-per-function": "off",
"max-nested-callbacks": "error",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-comment-style": ["error", "separate-lines"],
"multiline-ternary": "off",
"new-cap": ["error", {
"capIsNewExceptions": ["StringEnum", "StringKey"],
"capIsNewExceptionPattern": "^Type\\.",
"newIsCapExceptions": ["constructor"],
}],
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "error",
"no-array-constructor": "error",
"no-await-in-loop": "off",
"no-bitwise": "off",
"no-buffer-constructor": "error",
"no-caller": "error",
"no-catch-shadow": "off",
"no-confusing-arrow": "error",
"no-console": "error",
"no-constant-condition": ["error", {
"checkLoops": false,
}],
"no-constructor-return": "error",
"no-continue": "off",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty-function": "off",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-invalid-this": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": ["error", {
"groups": [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["in", "instanceof"],
],
}],
"no-multi-assign": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-native-reassign": "error",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-plusplus": ["error", {
"allowForLoopAfterthoughts": true,
}],
"no-proto": "error",
"no-restricted-exports": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-return-await": "off",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": ["error", {
"allow": ["Event", "Request", "yargs"],
}],
"no-spaced-func": "error",
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-undefined": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-use-before-define": "off",
"no-useless-backreference": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "off",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"no-void": ["error", {
"allowAsStatement": true,
}],
"no-warning-comments": "off",
"no-whitespace-before-property": "error",
"nonblock-statement-body-position": "error",
"object-curly-newline": "error",
"object-curly-spacing": "off",
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "error",
"operator-assignment": "off",
"operator-linebreak": ["error", "after", {
"overrides": {
"?": "before",
":": "before",
"||": "before",
"&&": "before",
"??": "before",
},
}],
"padded-blocks": "off",
"padding-line-between-statements": "error",
"prefer-arrow-callback": "error",
"prefer-const": "off",
"prefer-destructuring": "off",
"prefer-exponentiation-operator": "error",
"prefer-named-capture-group": "off",
"prefer-numeric-literals": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-regex-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"quote-props": "off",
"quotes": ["error", "double", {
"avoidEscape": true,
}],
"radix": ["error", "always"],
"require-atomic-updates": "off",
"require-await": "off",
"require-jsdoc": "off",
"require-unicode-regexp": "off",
"rest-spread-spacing": ["error", "never"],
"semi": "error",
"semi-spacing": "error",
"semi-style": "off",
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "error",
"space-before-function-paren": "off",
"space-in-parens": ["error", "never"],
"space-infix-ops": "off",
"space-unary-ops": "error",
"spaced-comment": ["error", "always", {
"line": {
"markers": ["/"],
},
}],
"switch-colon-spacing": "error",
"symbol-description": "error",
"template-curly-spacing": ["error", "never"],
"template-tag-spacing": "error",
"unicode-bom": ["error", "never"],
"vars-on-top": "error",
"wrap-iife": "error",
"wrap-regex": "off",
"yield-star-spacing": "error",
"yoda": ["error", "never", {
"exceptRange": true,
}],
"node/callback-return": "off",
"node/exports-style": "off",
"node/file-extension-in-import": "off",
"node/global-require": "off",
"node/handle-callback-err": "off",
"node/no-callback-literal": "off",
"node/no-deprecated-api": "off",
"node/no-exports-assign": "off",
"node/no-extraneous-import": "off",
"node/no-extraneous-require": "off",
"node/no-missing-import": "off",
"node/no-missing-require": "off",
"node/no-mixed-requires": "off",
"node/no-new-require": "off",
"node/no-path-concat": "off",
"node/no-process-env": "off",
"node/no-process-exit": "off",
"node/no-restricted-require": "off",
"node/no-sync": "error",
"node/no-unpublished-bin": "off",
"node/no-unpublished-import": "off",
"node/no-unpublished-require": "off",
"node/no-unsupported-features/es-builtins": "off",
"node/no-unsupported-features/es-syntax": "off",
"node/no-unsupported-features/node-builtins": "off",
"node/prefer-global/buffer": "off",
"node/prefer-global/console": "off",
"node/prefer-global/process": "off",
"node/prefer-global/text-decoder": "off",
"node/prefer-global/text-encoder": "off",
"node/prefer-global/url-search-params": "off",
"node/prefer-global/url": "off",
"node/prefer-promises/dns": "off",
"node/prefer-promises/fs": "off",
"node/process-exit-as-throw": "off",
"node/shebang": "off",
},
}, {
files: ["{packages,plugins}/**/*.ts?(x)"],
languageOptions: {
parser: tsParser,
},
extends: compat.extends("plugin:@typescript-eslint/recommended"),
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-shadow": ["error", {
"allow": ["Event", "Request", "yargs"],
}],
// Allow require imports where necessary (dynamic loading, webpack configs, etc.)
"@typescript-eslint/no-require-imports": "off",
"prefer-const": "off",
"no-shadow": "off",
},
}, {
files: ["test/**/*.js", "plugins/*/test/**/*.js"],
languageOptions: {
globals: {
...globals.mocha,
},
sourceType: "module",
parserOptions: {},
},
"rules": {
"prefer-arrow-callback": "off",
"no-invalid-this": "off",
"node/no-unpublished-import": "off",
"node/no-unpublished-require": "off",
},
}, {
files: [
"packages/web_ui/src/**/*.ts?(x)",
"packages/lib/browser.ts",
"packages/create/templates/*/web/**/*.{t,j}s?(x)",
"{packages,plugins}/*/web/**/*.ts?(x)",
],
languageOptions: {
globals: {
...globals.browser,
},
"sourceType": "module",
parserOptions: {
"ecmaFeatures": {
"jsx": true,
},
},
},
"rules": {
"node/callback-return": "off",
"node/exports-style": "off",
"node/file-extension-in-import": "off",
"node/global-require": "off",
"node/handle-callback-err": "off",
"node/no-callback-literal": "off",
"node/no-deprecated-api": "off",
"node/no-exports-assign": "off",
"node/no-extraneous-import": "off",
"node/no-extraneous-require": "off",
"node/no-missing-import": "off",
"node/no-missing-require": "off",
"node/no-mixed-requires": "off",
"node/no-new-require": "off",
"node/no-path-concat": "off",
"node/no-process-env": "off",
"node/no-process-exit": "off",
"node/no-restricted-require": "off",
"node/no-sync": "error",
"node/no-unpublished-bin": "off",
"node/no-unpublished-import": "off",
"node/no-unpublished-require": "off",
"node/no-unsupported-features/es-builtins": "off",
"node/no-unsupported-features/es-syntax": "off",
"node/no-unsupported-features/node-builtins": "off",
"node/prefer-global/buffer": "off",
"node/prefer-global/console": "off",
"node/prefer-global/process": "off",
"node/prefer-global/text-decoder": "off",
"node/prefer-global/text-encoder": "off",
"node/prefer-global/url-search-params": "off",
"node/prefer-global/url": "off",
"node/prefer-promises/dns": "off",
"node/prefer-promises/fs": "off",
"node/process-exit-as-throw": "off",
"node/shebang": "off",
},
}, {
files: ["packages/create/templates/**/*.?(c){t,j}s?(x)"],
"rules": {
"node/no-missing-require": "off",
"node/no-missing-import": "off",
"@stylistic/indent": "off",
"spaced-comment": "off",
"comma-spacing": "off",
"max-len": "off",
"indent": "off",
},
}, globalIgnores(["**/dist/", "dist/"])]);
+3 -9
View File
@@ -15,8 +15,8 @@
"cover": "c8 pnpm test",
"fast-cover": "FAST_TEST=y c8 pnpm test",
"ci-cover": "c8 --reporter=lcovonly pnpm test",
"lint": "eslint packages plugins test",
"lint-fix": "eslint --fix packages plugins test",
"lint": "vp lint packages plugins test",
"lint-fix": "vp lint --fix packages plugins test",
"clean": "node ./clean.js",
"docs": "typedoc",
"bundle-dependencies": "bundle-dependencies"
@@ -43,22 +43,16 @@
"@clusterio/host": "workspace:^",
"@clusterio/lib": "workspace:^",
"@clusterio/web_ui": "workspace:^",
"@eslint/eslintrc": "^3.3.7",
"@eslint/js": "^10.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"@types/node": "catalog:",
"c8": "^12.0.0",
"@typescript-eslint/eslint-plugin": "^8.71.2",
"@typescript-eslint/parser": "^8.71.2",
"eslint": "^10.13.0",
"eslint-plugin-node": "^11.1.0",
"express": "catalog:",
"form-data": "^4.0.6",
"globals": "^17.13.0",
"jsonwebtoken": "catalog:",
"mocha": "^11.8.0",
"typedoc": "^0.28.20",
"typescript": "catalog:",
"vite-plus": "^1.3.0",
"yargs": "catalog:",
"yauzl": "catalog:",
"yazl": "catalog:"
@@ -107,7 +107,7 @@ export class ControllerHooks extends lib.AsyncHookCollection {
* @param curr - The current value of the field.
* @param prev - The previous value of the field.
*/
// eslint-disable-next-line max-len
// eslint-disable-next-line @stylistic/max-len
readonly instanceConfigFieldChanged: lib.AsyncHook<[instance: InstanceRecord, field: string, curr: unknown, prev: unknown]>;
/**
@@ -165,7 +165,7 @@ export class ControllerHooks extends lib.AsyncHookCollection {
* The connection the event occured on.
* @param event - one of connect, drop, resume and close
*/
// eslint-disable-next-line max-len
// eslint-disable-next-line @stylistic/max-len
readonly hostConnectionEvent: lib.AsyncHook<[connection: HostConnection, event: "connect" | "drop" | "resume" | "close"]>;
/**
@@ -202,7 +202,7 @@ export class ControllerHooks extends lib.AsyncHookCollection {
* The connection the event occured on.
* @param event - one of connect, drop, resume, and close.
*/
// eslint-disable-next-line max-len
// eslint-disable-next-line @stylistic/max-len
readonly controlConnectionEvent: lib.AsyncHook<[connection: ControlConnection, event: "connect" | "drop" | "resume" | "close"]>;
/**
+1 -1
View File
@@ -52,7 +52,7 @@ type ControllerDebugEvents = {
type ControllerEvents = {
"stop": [],
}
};
/**
* Manages all controller related operations
+2 -2
View File
@@ -141,7 +141,7 @@ function getPlugins(req: Request, res: Response) {
// Note: Cast through unknown is needed because load_plugin is
// defined at runtime and no other fields in the controller config
// currently have the boolean type.
let web: { main:any, error:string|undefined } = { main: undefined, error: undefined};
let web: { main: any, error: string|undefined } = { main: undefined, error: undefined};
let devPlugins = req.app.locals.devPlugins;
if (devPlugins && devPlugins.has(name)) {
web.main = devPlugins.get(name);
@@ -305,7 +305,7 @@ type ProxyStreamEvents = {
"close": [],
"timeout": [],
"source": [],
}
};
export interface ProxyStream {
id: string;
+1
View File
@@ -1,3 +1,4 @@
// eslint-disable-next-line no-implicit-globals
declare function scriptError(err: any): void;
import("@clusterio/web_ui")
.then(webUi => webUi.bootstrap())
+1 -1
View File
@@ -2,7 +2,7 @@ import * as lib from "@clusterio/lib";
export async function getEditor(argsEditor: string) {
// eslint-disable-next-line
return argsEditor || process.env.EDITOR || process.env.VISUAL || undefined
return argsEditor || process.env.EDITOR || process.env.VISUAL || undefined;
// needed for the process.env statements to not be flagged by eslint
// priority for editors is CLI argument > env.EDITOR > env.VISUAL
}
+1 -1
View File
@@ -6,5 +6,5 @@ export interface BuildArgs {
build?: boolean;
outputDir: string;
sourceDir: string;
};
}
export function build(args: BuildArgs): Promise<void>;
+2
View File
@@ -264,7 +264,9 @@ export class LockFile {
// Event handler for synchronous process exit, readLock can't be used as it is async
private handleExitSync = (): void => {
try {
// eslint-disable-next-line node/no-sync
if (readFileSync(this.filePath, "utf8") === LockFile.content) {
// eslint-disable-next-line node/no-sync
unlinkSync(this.filePath);
}
} catch (err) {
+1 -1
View File
@@ -14,4 +14,4 @@ export type PluginWebApi = {
* package.
*/
npmPackage?: string;
}
};
+8 -8
View File
@@ -24,7 +24,7 @@ type OldConfigGroupSchema = Static<typeof OldConfigGroupSchema>;
const OldConfigSchema = Type.Object({
"groups": Type.Array(OldConfigGroupSchema),
});
export type OldConfigSchema = Static<typeof OldConfigSchema>
export type OldConfigSchema = Static<typeof OldConfigSchema>;
export const ConfigSchema = Type.Record(Type.String(), Type.Union([
Type.Null(),
@@ -38,11 +38,11 @@ export type ConfigSchema = Static<typeof ConfigSchema>;
type FieldValue = null | boolean | string | number | object;
type FieldType<T extends FieldValue> =
T extends boolean ? "boolean" :
T extends string ? "string" :
T extends number ? "number" :
T extends object ? "object" :
never;
T extends boolean ? "boolean"
: T extends string ? "string"
: T extends number ? "number"
: T extends object ? "object"
: never;
export interface FieldDefinition<
Type extends FieldValue = any,
@@ -134,7 +134,7 @@ export type ConfigDefs<
Fields extends Record<keyof Fields, FieldValue>
> = {
[Prop in keyof Fields]: FieldDefinition<Fields[Prop], Fields>;
}
};
function defaultValue(def: FieldDefinition) {
if (def.initialValue === undefined) {
@@ -148,7 +148,7 @@ function defaultValue(def: FieldDefinition) {
export type ConfigEvents = {
fieldChanged: [ name: string, curr: FieldValue, prev: FieldValue ],
}
};
export enum ConfigAccess {
read = 0x1,
+2 -2
View File
@@ -15,8 +15,8 @@ export abstract class DatastoreProvider<
K extends DatastoreKey,
V extends DatastoreValue,
> {
abstract save(data: Map<K, V>): Promise<void>
abstract load(): Promise<Map<K, V>>
abstract save(data: Map<K, V>): Promise<void>;
abstract load(): Promise<Map<K, V>>;
}
// Store all data in memory, used as a default and should be avoided
+6 -6
View File
@@ -35,7 +35,7 @@ export type FactorioModSettingType = "startup" | "runtime-global" | "runtime-per
* Boolean mod setting prototype.
* @see [bool-setting](https://wiki.factorio.com/Tutorial:Mod_settings#bool-setting)
*/
export interface FactorioBoolSettingPrototype extends FactorioPrototypeBase{
export interface FactorioBoolSettingPrototype extends FactorioPrototypeBase {
type: "bool-setting",
setting_type?: FactorioModSettingType,
default_value: boolean,
@@ -46,7 +46,7 @@ export interface FactorioBoolSettingPrototype extends FactorioPrototypeBase{
* Integer mod setting prototype.
* @see [int-setting](https://wiki.factorio.com/Tutorial:Mod_settings#int-setting)
*/
export interface FactorioIntSettingPrototype extends FactorioPrototypeBase{
export interface FactorioIntSettingPrototype extends FactorioPrototypeBase {
type: "int-setting",
setting_type?: FactorioModSettingType,
default_value: number,
@@ -59,7 +59,7 @@ export interface FactorioIntSettingPrototype extends FactorioPrototypeBase{
* Real number mod setting prototype.
* @see [double-setting](https://wiki.factorio.com/Tutorial:Mod_settings#double-setting)
*/
export interface FactorioDoubleSettingPrototype extends FactorioPrototypeBase{
export interface FactorioDoubleSettingPrototype extends FactorioPrototypeBase {
type: "double-setting",
setting_type?: FactorioModSettingType,
default_value: number,
@@ -72,7 +72,7 @@ export interface FactorioDoubleSettingPrototype extends FactorioPrototypeBase{
* String mod setting prototype.
* @see [string-setting](https://wiki.factorio.com/Tutorial:Mod_settings#string-setting)
*/
export interface FactorioStringSettingPrototype extends FactorioPrototypeBase{
export interface FactorioStringSettingPrototype extends FactorioPrototypeBase {
type: "string-setting",
setting_type?: FactorioModSettingType,
default_value: string,
@@ -85,7 +85,7 @@ export interface FactorioStringSettingPrototype extends FactorioPrototypeBase{
* Color mod setting prototype.
* @see [color-setting](https://wiki.factorio.com/Tutorial:Mod_settings#color-setting)
*/
export interface FactorioColorSettingPrototype extends FactorioPrototypeBase{
export interface FactorioColorSettingPrototype extends FactorioPrototypeBase {
type: "color-setting",
setting_type?: FactorioModSettingType,
default_value: FactorioColor,
@@ -104,7 +104,7 @@ export type FactorioModSettingPrototype =
* A list of prototypes as it appears in Factorio's Lua API.
* @see [Data.raw](https://lua-api.factorio.com/stable/types/Data.html#raw)
*/
export type FactorioPrototypes<Prototype = FactorioPrototypeBase> = Record<string, Record<string, Prototype>>
export type FactorioPrototypes<Prototype = FactorioPrototypeBase> = Record<string, Record<string, Prototype>>;
export type FactorioDefinesValue = number | string | { [index: string]: FactorioDefinesValue };
export type FactorioDefines = {
[index: string]: FactorioDefinesValue,
+2 -2
View File
@@ -12,7 +12,7 @@ export const ExternalFactorioVersionSchema = Type.Object({
headlessUrl: Type.String(),
});
export type ExternalFactorioVersion = Static<typeof ExternalFactorioVersionSchema>
export type ExternalFactorioVersion = Static<typeof ExternalFactorioVersionSchema>;
/**
* Fetch all factorio versions that support headless
@@ -47,7 +47,7 @@ function parseFactorioVersions(html: string): ExternalFactorioVersion[] {
const versions = new Map<string, ExternalFactorioVersion>();
// Match <a class=" slot-button-inline version-button-stable " href="/download/archive/2.0.72">2.0.72</a>
// eslint-disable-next-line max-len
// eslint-disable-next-line @stylistic/max-len
const anchorRegex = /<a[^>]*class="[^"]*version-button-(?<kind>stable|experimental)[^"]*"[^>]*href="[^"]*\/download\/archive\/(?<version>\d+\.\d+.\d+)"[^>]*>/g;
let match: RegExpExecArray | null;
+1 -1
View File
@@ -15,7 +15,7 @@ export const LatestReleaseBuildsSchema = Type.Record(Type.String(), FullVersionS
*/
export const LatestReleasesSchema = Type.Record(Type.String(), LatestReleaseBuildsSchema);
export type LatestReleases = Static<typeof LatestReleasesSchema>
export type LatestReleases = Static<typeof LatestReleasesSchema>;
/**
* Fetch the latest stable and experimental factorio releases
+3 -3
View File
@@ -177,9 +177,9 @@ export const logger = winston.createLogger({
// Who in their right mind thought that log levels should be hard coded
// into the type when you can define custom levels you want to support?
export type Logger = Omit<winston.Logger,
"error" | "warn" | "help" | "data" | "info" | "debug" | "prompt" | "verbose" | "input" | "silly" |
"emerg" | "alert" | "crit" | "warning" | "notice" |
"child"
"error" | "warn" | "help" | "data" | "info" | "debug" | "prompt" | "verbose" | "input" | "silly"
| "emerg" | "alert" | "crit" | "warning" | "notice"
| "child"
> & {
fatal: winston.LeveledLogMethod,
error: winston.LeveledLogMethod,
+1 -1
View File
@@ -46,7 +46,7 @@ export type PluginDeclaration = {
errors?: ErrorClass[];
permissions?: PermissionDefinition[];
routes?: string[];
}
};
/**
* Check if a plugin is expected to ship a web build.
+1 -1
View File
@@ -175,7 +175,7 @@ export async function handlePluginCommand(
try {
const pluginPackage = (await import(
pathToFileURL(path.posix.join(pluginPath, "package.json")).href,
{ with: { type: "json" }},
{ with: { type: "json" }}
)).default;
pluginInfo = (await import(
pathToFileURL(path.posix.join(pluginPath, pluginPackage.main ?? "index.js")).href
+2 -2
View File
@@ -7,7 +7,7 @@ import { SessionLost, ValidationError } from "./errors.js";
import { logger } from "./logging.js";
export type SubscriptionRequestHandler<T> = RequestHandler<SubscriptionRequest, Event<T> | null>;
export type EventSubscriberCallback<T> = (event: T | null, synced: boolean) => void
export type EventSubscriberCallback<T> = (event: T | null, synced: boolean) => void;
/**
* Represents a set of string subscription filters.
@@ -241,7 +241,7 @@ type Subscriber = {
link: Link,
address: Address,
filters: SubscriptionFilters,
}
};
type EventData = {
subscriptionUpdate?: SubscriptionRequestHandler<unknown>,
+1 -1
View File
@@ -157,7 +157,7 @@ npm init "@clusterio" -- --controller-token ${token} --mode "host" --download-he
</>;
}
function CopyButton({ text, message }: { text:string, message:string }) {
function CopyButton({ text, message }: { text: string, message: string }) {
let [clipboardPermision, setClipboardPermission] = useState<PermissionState>("granted");
useEffect(() => {
(async () => {
@@ -264,7 +264,7 @@ type DownloadDependenciesProps = {
mods: lib.ModRecord[];
onChange: (change: ModChange) => void;
builtInModNames: string[];
}
};
function DownloadDependenciesButton(props: DownloadDependenciesProps) {
const control = useContext(ControlContext);
const [modInfos] = useMods();
@@ -904,7 +904,7 @@ type SettingsTableFieldProps = {
allowedValues: any;
locale: Map<string, any>;
changed: boolean;
}
};
const SettingsTableField = memo((props: SettingsTableFieldProps) => {
const name = props.name;
const isBoolean = props.type === "bool-setting";
+2 -2
View File
@@ -25,7 +25,7 @@ type ModalProps = {
disabled: boolean;
save: lib.SaveDetails;
instanceId: number;
}
};
function RenameModal(props: ModalProps) {
let control = useContext(ControlContext);
@@ -198,7 +198,7 @@ type File = {
name: string;
percent: number;
status: string;
}
};
export default function SavesList(props: { instance: lib.InstanceDetails }) {
let account = useAccount();
+1 -1
View File
@@ -65,7 +65,7 @@ type UserBulkActionProps = {
setApplyAction(func: () => Promise<void>): void,
form: FormInstance,
restore?: boolean,
}
};
function UserBulkActionImport({ setApplyAction, form, restore }: UserBulkActionProps) {
const control = useContext(ControlContext);
+1 -1
View File
@@ -9,7 +9,7 @@ type IpcPlayerAuth = {
type: "set_verify_code",
player: string,
verify_code: string,
}
};
export default async function loadInstancePlugin(context: InstancePluginContext) {
const { instance, host, logger, plugin } = context;
+4 -4
View File
@@ -10,12 +10,12 @@ import { type Component, enabledComponents, stripPlayerData } from "./components
type IpcPlayerName = {
player_name: string
}
};
type IpcDownloadRequest = {
player_name: string,
recipe_notifications?: string,
}
};
/** Components as an escaped JSON object for the scenario. */
function componentsJson(enabled: Set<Component>) {
@@ -30,7 +30,7 @@ type IpcAcquireResponse = {
message?: string,
epoch?: number,
token?: string,
}
};
/**
* Splits string into array of strings with max of a certain length
@@ -64,7 +64,7 @@ export type RecipeNotificationDelta = {
remove?: string[],
/** add is the whole stored list, sent without the instance's: the save works out what to remove */
full?: true,
}
};
/**
* Compute the changes needed to turn the current cleared recipe list into the stored one
+1 -1
View File
@@ -30,7 +30,7 @@ export type IpcPlayerData = {
// Components the upload was serialized with, all when missing
components?: Record<string, boolean> | unknown[],
destination_instance_id?: number, // set_destination's instance, on the leave upload only; not player data
}
};
// Marks tombstones in player_sync.json
const jsonReserved = { deleted: Type.Optional(Type.Never()) };
+1 -1
View File
@@ -22,7 +22,7 @@ type Technology = {
level: number,
progress: number | null,
researched: boolean,
}
};
async function loadTechnologies(
+4 -4
View File
@@ -145,21 +145,21 @@ describe("research_sync controller", function() {
});
describe("technology sync unlocking several levels", function() {
const techSync = (name, level, researched) => mock.getHandler(controller, SyncTechnologiesRequest)(
const syncTech = (name, level, researched) => mock.getHandler(controller, SyncTechnologiesRequest)(
new SyncTechnologiesRequest([new TechnologySync(name, level, null, researched)]), src
);
it("should drop one entry per level unlocked on a known technology", async function() {
await techSync("m", 4, true);
await syncTech("m", 4, true);
await update(["m", "m", "m", "x"]);
sent.length = 0;
await techSync("m", 7, true);
await syncTech("m", 7, true);
assert.deepEqual(await sync(), ["x"]);
assert.equal(queueEvents().length, 1);
assert.deepEqual(queueEvents()[0][1], new QueueEvent(["x"], null, null));
});
it("should drop one entry per level unlocked on a new technology", async function() {
await update(["m", "m", "x"]);
await techSync("m", 2, true);
await syncTech("m", 2, true);
assert.deepEqual(await sync(), ["x"]);
});
});
+2 -2
View File
@@ -26,12 +26,12 @@ type IpcStats = {
weight: number,
}
}
}
};
type IpcFlowType = {
"input": Record<string, number>,
"output": Record<string, number>,
}
};
const instancePlayerCount = new Gauge(
"clusterio_statistics_exporter_instance_player_count",
+4 -2
View File
@@ -78,7 +78,9 @@ export class NeuralDole {
}
}
divider(object: { name:string, quality:string, count:number, instanceId:number, instanceName:string }): number {
divider(
object: { name: string, quality: string, count: number, instanceId: number, instanceName: string }
): number {
let magicData = doleNN.dose(
object.count, // numReq
this.items.getItemCount(object.name, object.quality) || 0,
@@ -118,7 +120,7 @@ export function doleDivider(
logItemTransfers,
logger,
} :{
object: { name:string, quality:string, count:number, instanceId:number, instanceName:string },
object: { name: string, quality: string, count: number, instanceId: number, instanceName: string },
items: lib.ItemDatabase,
logItemTransfers: boolean,
logger: lib.Logger,
+1 -1
View File
@@ -181,7 +181,7 @@ describe("host/server", function() {
let port = hostServer._randomDynamicPort();
assert.equal(typeof port, "number");
assert(Number.isInteger(port));
assert(0 <= port && port < 2**16);
assert(port >= 0 && port < 2**16);
});
it("should return a port number in the dynamic range", function() {
+1 -1
View File
@@ -1313,7 +1313,7 @@ describe("Integration of Clusterio", function() {
} else if (isV2) {
exchangeString = testStrings.modified_v2;
}
// eslint-disable-next-line max-len
// eslint-disable-next-line @stylistic/max-len
const args = `base/freeplay --seed 1234 --map-exchange-string "${exchangeString.replace(/[\n\r]+/g, "")}"`;
await execCtl("instance stop 44");
+371
View File
@@ -0,0 +1,371 @@
import { defineConfig } from "vite-plus";
// Formatting rules come from @stylistic as Oxlint does not implement them.
export default defineConfig({
lint: {
plugins: ["node"],
jsPlugins: ["@stylistic/eslint-plugin"],
categories: {
correctness: "off",
},
env: {
builtin: true,
node: true,
},
ignorePatterns: ["**/dist/", "dist/"],
rules: {
"@stylistic/indent": [
"error",
"tab",
{
ignoredNodes: ["JSXFragment"],
ignoreComments: true,
SwitchCase: 1,
},
],
"accessor-pairs": "error",
"array-callback-return": "error",
"arrow-body-style": "error",
"block-scoped-var": "error",
complexity: [
"error",
{
max: 25,
},
],
curly: "error",
"default-case": "error",
"default-case-last": "error",
eqeqeq: "error",
"func-name-matching": "error",
"func-style": [
"error",
"declaration",
{
allowArrowFunctions: true,
},
],
"grouped-accessor-pairs": "error",
"guard-for-in": "error",
"id-match": "error",
"max-depth": "error",
"max-nested-callbacks": "error",
"new-cap": [
"error",
{
capIsNewExceptions: ["StringEnum", "StringKey"],
capIsNewExceptionPattern: "^Type\\.",
newIsCapExceptions: ["constructor"],
},
],
"no-alert": "error",
"no-array-constructor": "error",
"no-caller": "error",
"no-console": "error",
"no-constant-condition": [
"error",
{
checkLoops: false,
},
],
"no-constructor-return": "error",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-multi-assign": "error",
"no-multi-str": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-plusplus": [
"error",
{
allowForLoopAfterthoughts: true,
},
],
"no-proto": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-properties": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": [
"error",
{
allow: ["Event", "Request", "yargs"],
},
],
"no-template-curly-in-string": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-useless-backreference": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"no-void": [
"error",
{
allowAsStatement: true,
},
],
"prefer-arrow-callback": "error",
"prefer-exponentiation-operator": "error",
"prefer-numeric-literals": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-regex-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
radix: ["error", "always"],
"symbol-description": "error",
"unicode-bom": ["error", "never"],
"vars-on-top": "error",
yoda: [
"error",
"never",
{
exceptRange: true,
},
],
"node/no-sync": [
"error",
{
// Oxlint also reports identifiers ending in Sync that are not calls
ignores: ["TechnologySync", "techsToSync"],
},
],
"@stylistic/array-bracket-newline": "off",
"@stylistic/array-bracket-spacing": ["error", "never"],
"@stylistic/array-element-newline": "off",
"@stylistic/arrow-parens": "off",
"@stylistic/arrow-spacing": [
"error",
{
after: true,
before: true,
},
],
"@stylistic/block-spacing": "error",
"@stylistic/brace-style": [
"error",
"1tbs",
{
allowSingleLine: true,
},
],
"@stylistic/comma-dangle": [
"error",
{
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "only-multiline",
generics: "only-multiline",
enums: "always-multiline",
tuples: "always-multiline",
},
],
"@stylistic/comma-spacing": "error",
"@stylistic/comma-style": ["error", "last"],
"@stylistic/computed-property-spacing": ["error", "never"],
"@stylistic/dot-location": ["error", "property"],
"@stylistic/eol-last": "error",
"@stylistic/function-call-argument-newline": "off",
"@stylistic/function-paren-newline": "off",
"@stylistic/generator-star-spacing": [
"error",
{
before: false,
after: true,
},
],
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/jsx-quotes": "error",
"@stylistic/key-spacing": "error",
"@stylistic/keyword-spacing": "error",
"@stylistic/line-comment-position": "off",
"@stylistic/linebreak-style": ["error", "unix"],
"@stylistic/lines-around-comment": "off",
"@stylistic/lines-between-class-members": [
"error",
"always",
{
exceptAfterSingleLine: true,
},
],
"@stylistic/max-len": [
"error",
{
code: 120,
},
],
"@stylistic/max-statements-per-line": "off",
"@stylistic/multiline-comment-style": ["error", "separate-lines"],
"@stylistic/multiline-ternary": "off",
"@stylistic/new-parens": "error",
"@stylistic/newline-per-chained-call": "off",
"@stylistic/no-confusing-arrow": "error",
"@stylistic/no-extra-parens": "off",
"@stylistic/no-floating-decimal": "error",
"@stylistic/no-mixed-operators": [
"error",
{
groups: [
["&", "|", "^", "~", "<<", ">>", ">>>"],
["==", "!=", "===", "!==", ">", ">=", "<", "<="],
["in", "instanceof"],
],
},
],
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-multiple-empty-lines": "error",
"@stylistic/no-tabs": "off",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/no-whitespace-before-property": "error",
"@stylistic/nonblock-statement-body-position": "error",
"@stylistic/object-curly-newline": "error",
"@stylistic/object-curly-spacing": "off",
"@stylistic/one-var-declaration-per-line": "error",
"@stylistic/operator-linebreak": [
"error",
"after",
{
overrides: {
"?": "before",
":": "before",
"||": "before",
"&&": "before",
"??": "before",
"|": "before",
"&": "before",
},
},
],
"@stylistic/padded-blocks": "off",
"@stylistic/padding-line-between-statements": "error",
"@stylistic/quote-props": "off",
"@stylistic/quotes": [
"error",
"double",
{
avoidEscape: true,
},
],
"@stylistic/rest-spread-spacing": ["error", "never"],
"@stylistic/semi": "error",
"@stylistic/semi-spacing": "error",
"@stylistic/semi-style": "off",
"@stylistic/space-before-blocks": "error",
"@stylistic/space-before-function-paren": "off",
"@stylistic/space-in-parens": ["error", "never"],
"@stylistic/space-infix-ops": "off",
"@stylistic/space-unary-ops": "error",
"@stylistic/spaced-comment": [
"error",
"always",
{
line: {
markers: ["/"],
},
},
],
"@stylistic/switch-colon-spacing": "error",
"@stylistic/template-curly-spacing": ["error", "never"],
"@stylistic/template-tag-spacing": "error",
"@stylistic/wrap-iife": "error",
"@stylistic/wrap-regex": "off",
"@stylistic/yield-star-spacing": "error",
},
overrides: [
{
files: ["{packages,plugins}/**/*.{ts,tsx}"],
rules: {
"no-unused-vars": "off",
"typescript/ban-ts-comment": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-empty-object-type": "error",
"typescript/no-explicit-any": "off",
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-misused-new": "error",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "error",
"typescript/no-require-imports": "off",
"typescript/no-this-alias": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-declaration-merging": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-wrapper-object-types": "error",
"typescript/prefer-as-const": "error",
"typescript/prefer-namespace-keyword": "error",
"typescript/triple-slash-reference": "off",
"no-shadow": [
"error",
{
allow: ["Event", "Request", "yargs"],
},
],
"no-useless-constructor": "error",
},
plugins: ["typescript"],
},
{
files: ["test/**/*.js", "plugins/*/test/**/*.js"],
rules: {
"prefer-arrow-callback": "off",
},
env: {
mocha: true,
},
},
{
files: [
"packages/web_ui/src/**/*.{ts,tsx}",
"packages/lib/browser.ts",
"packages/create/templates/*/web/**/*.{ts,js,tsx,jsx}",
"{packages,plugins}/*/web/**/*.{ts,tsx}",
],
globals: {
SpeechRecognitionAlternative: "readonly",
SpeechRecognitionResult: "readonly",
SpeechRecognitionResultList: "readonly",
},
env: {
browser: true,
},
},
{
files: ["packages/create/templates/**/*.{js,cjs,ts,cts,jsx,tsx}"],
rules: {
"@stylistic/indent": "off",
"@stylistic/spaced-comment": "off",
"@stylistic/comma-spacing": "off",
"@stylistic/max-len": "off",
},
jsPlugins: ["@stylistic/eslint-plugin"],
},
],
},
});