Skip to content
Merged
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
28 changes: 10 additions & 18 deletions apps/oxlint/src-js/package/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@ import type { SourceCode } from "../plugins/source_code.ts";
import type { BeforeHook, Visitor, VisitorWithHooks } from "../plugins/types.ts";
import type { SetNullable } from "../utils/types.ts";

const {
defineProperty,
getPrototypeOf,
hasOwn,
setPrototypeOf,
create: ObjectCreate,
freeze,
assign: ObjectAssign,
} = Object;

// Empty visitor object, returned by `create` when `before` hook returns `false`.
const EMPTY_VISITOR: Visitor = {};

Expand Down Expand Up @@ -48,7 +38,7 @@ export function definePlugin(plugin: Plugin): Plugin {

// Make each rule in the plugin ESLint-compatible by calling `defineRule` on it
for (const ruleName in rules) {
if (hasOwn(rules, ruleName)) {
if (Object.hasOwn(rules, ruleName)) {
rules[ruleName] = defineRule(rules[ruleName]);
}
}
Expand Down Expand Up @@ -91,10 +81,12 @@ export function defineRule(rule: Rule): Rule {
// Copy properties from ESLint's context object to `context`.
// ESLint's context object is an object of form `{ id, options, report }`, with all other properties
// and methods on another object which is its prototype.
defineProperty(context, "id", { value: eslintContext.id });
defineProperty(context, "options", { value: eslintContext.options });
defineProperty(context, "report", { value: eslintContext.report });
setPrototypeOf(context, getPrototypeOf(eslintContext));
Object.defineProperties(context, {
id: { value: eslintContext.id },
options: { value: eslintContext.options },
report: { value: eslintContext.report },
});
Object.setPrototypeOf(context, Object.getPrototypeOf(eslintContext));

// If `before` hook returns `false`, skip traversal by returning an empty object as visitor
if (beforeHook !== null) {
Expand All @@ -119,7 +111,7 @@ let cwd: string | null = null;
// All other getters/methods throw, same as they do in main implementation.
//
// See `FILE_CONTEXT` in `plugins/context.ts` for details of all the getters/methods.
const FILE_CONTEXT: FileContext = freeze({
const FILE_CONTEXT: FileContext = Object.freeze({
get filename(): string {
throw new Error("Cannot access `context.filename` in `createOnce`");
},
Expand Down Expand Up @@ -165,7 +157,7 @@ const FILE_CONTEXT: FileContext = freeze({

extend(this: FileContext, extension: Record<string | number | symbol, unknown>): FileContext {
// Note: We can allow calling `extend` in `createOnce`, as it involves no file-specific state
return freeze(ObjectAssign(ObjectCreate(this), extension));
return Object.freeze(Object.assign(Object.create(this), extension));
},

get parserOptions(): Record<string, unknown> {
Expand Down Expand Up @@ -201,7 +193,7 @@ function createContextAndVisitor(rule: CreateOnceRule): {
// Really, accessing `options` or calling `report` should throw, because they're illegal in `createOnce`.
// But any such bugs should have been caught when testing the rule in Oxlint, so should be OK to take this shortcut.
// `FILE_CONTEXT` prototype provides `cwd` property and `extends` method, which are available in `createOnce`.
const context: Context = ObjectCreate(FILE_CONTEXT, {
const context: Context = Object.create(FILE_CONTEXT, {
id: { value: "", enumerable: true, configurable: true },
options: { value: null, enumerable: true, configurable: true },
report: { value: null, enumerable: true, configurable: true },
Expand Down
45 changes: 22 additions & 23 deletions apps/oxlint/src-js/package/rule_tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ import type { Options } from "../plugins/options.ts";
import type { DiagnosticData, Suggestion } from "../plugins/report.ts";
import type { ParseOptions } from "./parse.ts";

const { hasOwn } = Object,
{ isArray } = Array,
jsonStringify = JSON.stringify;

// ------------------------------------------------------------------------------
// `describe` and `it` functions
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -555,18 +551,21 @@ function assertInvalidTestCaseMessageIsCorrect(
messages: Record<string, string> | null,
): void {
// Check `message` property
if (hasOwn(error, "message")) {
if (Object.hasOwn(error, "message")) {
// Check `message` property
assert(
!hasOwn(error, "messageId"),
!Object.hasOwn(error, "messageId"),
"Error should not specify both `message` and a `messageId`",
);
assert(!hasOwn(error, "data"), "Error should not specify both `data` and `message`");
assert(!Object.hasOwn(error, "data"), "Error should not specify both `data` and `message`");
assertMessageMatches(diagnostic.message, error.message!);
return;
}

assert(hasOwn(error, "messageId"), "Test error must specify either a `messageId` or `message`");
assert(
Object.hasOwn(error, "messageId"),
"Test error must specify either a `messageId` or `message`",
);

// Check `messageId` property
assert(
Expand All @@ -575,7 +574,7 @@ function assertInvalidTestCaseMessageIsCorrect(
);

const messageId: string = error.messageId!;
if (!hasOwn(messages, messageId)) {
if (!Object.hasOwn(messages, messageId)) {
const legalMessageIds = `[${Object.keys(messages)
.map((key) => `'${key}'`)
.join(", ")}]`;
Expand Down Expand Up @@ -607,7 +606,7 @@ function assertInvalidTestCaseMessageIsCorrect(
);
}

if (hasOwn(error, "data")) {
if (Object.hasOwn(error, "data")) {
// If data was provided, then directly compare the returned message to a synthetic
// interpolated message using the same message ID and data provided in the test
const rehydratedMessage = replacePlaceholders(ruleMessage, error.data!);
Expand Down Expand Up @@ -644,12 +643,12 @@ function assertInvalidTestCaseLocationIsCorrect(

const columnOffset = test.eslintCompat === true ? 1 : 0;

if (hasOwn(error, "line")) {
if (Object.hasOwn(error, "line")) {
actualLocation.line = diagnostic.line;
expectedLocation.line = error.line;
}

if (hasOwn(error, "column")) {
if (Object.hasOwn(error, "column")) {
actualLocation.column = diagnostic.column + columnOffset;
expectedLocation.column = error.column;
}
Expand All @@ -666,7 +665,7 @@ function assertInvalidTestCaseLocationIsCorrect(
diagnostic.endLine === diagnostic.line &&
diagnostic.endColumn === diagnostic.column;

if (hasOwn(error, "endLine")) {
if (Object.hasOwn(error, "endLine")) {
if (error.endLine === undefined && canVoidEndLocation) {
actualLocation.endLine = undefined;
} else {
Expand All @@ -675,7 +674,7 @@ function assertInvalidTestCaseLocationIsCorrect(
expectedLocation.endLine = error.endLine;
}

if (hasOwn(error, "endColumn")) {
if (Object.hasOwn(error, "endColumn")) {
if (error.endColumn === undefined && canVoidEndLocation) {
actualLocation.endColumn = undefined;
} else {
Expand Down Expand Up @@ -1068,7 +1067,7 @@ function setupOptions(test: TestCase): number {
// Serialize to JSON and pass to `setOptions`
let allOptionsJson: string;
try {
allOptionsJson = jsonStringify({ options: allOptions, ruleIds: allRuleIds });
allOptionsJson = JSON.stringify({ options: allOptions, ruleIds: allRuleIds });
} catch (err) {
throw new Error(`Failed to serialize options: ${err}`);
}
Expand Down Expand Up @@ -1130,7 +1129,7 @@ function getTestName(test: TestCase): string {
* @throws {*} - Value thrown by the hook function
*/
function runBeforeHook(test: TestCase): void {
if (hasOwn(test, "before")) runHook(test, test.before, "before");
if (Object.hasOwn(test, "before")) runHook(test, test.before, "before");
}

/**
Expand All @@ -1140,7 +1139,7 @@ function runBeforeHook(test: TestCase): void {
* @throws {*} - Value thrown by the hook function
*/
function runAfterHook(test: TestCase): void {
if (hasOwn(test, "after")) runHook(test, test.after, "after");
if (Object.hasOwn(test, "after")) runHook(test, test.after, "after");
}

/**
Expand Down Expand Up @@ -1216,15 +1215,15 @@ function assertInvalidTestCaseIsWellFormed(
`Did not specify errors for an invalid test of rule \`${ruleName}\``,
);
assert(
isArray(errors),
Array.isArray(errors),
`Invalid 'errors' property for invalid test of rule \`${ruleName}\`:` +
`expected a number or an array but got ${errors === null ? "null" : typeof errors}`,
);
assert(errors.length !== 0, "Invalid cases must have at least one error");
}

// `output` is optional, but if it exists it must be a string or `null`
if (hasOwn(test, "output")) {
if (Object.hasOwn(test, "output")) {
assert(
test.output === null || typeof test.output === "string",
"Test property `output`, if specified, must be a string or null. " +
Expand All @@ -1247,16 +1246,16 @@ function assertTestCaseCommonPropertiesAreWellFormed(test: TestCase): void {
if (test.name) {
assert(typeof test.name === "string", "Optional test case property `name` must be a string");
}
if (hasOwn(test, "only")) {
if (Object.hasOwn(test, "only")) {
assert(typeof test.only === "boolean", "Optional test case property `only` must be a boolean");
}
if (hasOwn(test, "filename")) {
if (Object.hasOwn(test, "filename")) {
assert(
typeof test.filename === "string",
"Optional test case property `filename` must be a string",
);
}
if (hasOwn(test, "options")) {
if (Object.hasOwn(test, "options")) {
assert(Array.isArray(test.options), "Optional test case property `options` must be an array");
}
}
Expand Down Expand Up @@ -1330,7 +1329,7 @@ function isSerializablePrimitiveOrPlainObject(value: unknown): boolean {
typeof value === "string" ||
typeof value === "boolean" ||
typeof value === "number" ||
(typeof value === "object" && (value.constructor === Object || isArray(value)))
(typeof value === "object" && (value.constructor === Object || Array.isArray(value)))
);
}

Expand Down
20 changes: 9 additions & 11 deletions apps/oxlint/src-js/plugins/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ import type { Settings } from "./settings.ts";
import type { SourceCode } from "./source_code.ts";
import type { ModuleKind, Program } from "../generated/types.d.ts";

const { freeze, assign: ObjectAssign, create: ObjectCreate } = Object;

// Cached current working directory
let cwd: string | null = null;

Expand Down Expand Up @@ -73,13 +71,13 @@ export const ECMA_VERSION = 2026;
const ECMA_VERSION_NUMBER = 17;

// Supported ECMAScript versions. This matches ESLint's default.
const SUPPORTED_ECMA_VERSIONS = freeze([3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);
const SUPPORTED_ECMA_VERSIONS = Object.freeze([3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);

// Singleton object for parser's `Syntax` property. Generated lazily.
let Syntax: Record<string, string> | null = null;

// Singleton object for parser.
const PARSER = freeze({
const PARSER = Object.freeze({
/**
* Parser name.
*/
Expand Down Expand Up @@ -113,11 +111,11 @@ const PARSER = freeze({
get Syntax(): Readonly<Record<string, string>> {
// Construct lazily, as it's probably rarely used
if (Syntax === null) {
Syntax = ObjectCreate(null);
Syntax = Object.create(null);
for (const key in visitorKeys) {
Syntax![key] = key;
}
freeze(Syntax);
Object.freeze(Syntax);
}
return Syntax!;
},
Expand All @@ -135,7 +133,7 @@ const PARSER = freeze({

// Singleton object for parser options.
// TODO: `sourceType` is the only property ESLint provides. But does TS-ESLint provide any further properties?
const PARSER_OPTIONS = freeze({
const PARSER_OPTIONS = Object.freeze({
/**
* Source type of the file being linted.
*/
Expand Down Expand Up @@ -211,7 +209,7 @@ if (CONFORMANCE) {
});
}

freeze(LANGUAGE_OPTIONS);
Object.freeze(LANGUAGE_OPTIONS);

/**
* Language options used when parsing a file.
Expand Down Expand Up @@ -245,7 +243,7 @@ export type LanguageOptions = Readonly<typeof LANGUAGE_OPTIONS>;
// TODO: When we write a rule tester, throw an error in the tester if the rule uses deprecated methods/getters.
// We'll need to offer an option to opt out of these errors, for rules which delegate to another rule whose code
// the author doesn't control.
const FILE_CONTEXT = freeze({
const FILE_CONTEXT = Object.freeze({
/**
* Absolute path of the file being linted.
*/
Expand Down Expand Up @@ -360,7 +358,7 @@ const FILE_CONTEXT = freeze({
*/
extend(this: FileContext, extension: Record<string | number | symbol, unknown>): FileContext {
// Note: We can allow calling `extend` in `createOnce`, as it involves no file-specific state
return freeze(ObjectAssign(ObjectCreate(this), extension));
return Object.freeze(Object.assign(Object.create(this), extension));
},

/**
Expand Down Expand Up @@ -436,7 +434,7 @@ export function createContext(fullRuleName: string, ruleDetails: RuleDetails): R
// IMPORTANT: Methods/getters must not use `this`, to support wrapped context objects
// or e.g. `const { report } = context; report(diagnostic);`.
// https://github.com/oxc-project/oxc/issues/15325
return freeze({
return Object.freeze({
// Inherit from `FILE_CONTEXT`, which provides getters for file-specific properties
__proto__: FILE_CONTEXT,
// Rule ID, in form `<plugin>/<rule>`
Expand Down
21 changes: 8 additions & 13 deletions apps/oxlint/src-js/plugins/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ import type { RuleDetails } from "./load.ts";
import type { Range, Ranged } from "./location.ts";
import type { Diagnostic } from "./report.ts";

const { prototype: ArrayPrototype, from: ArrayFrom } = Array,
{ getPrototypeOf, hasOwn, prototype: ObjectPrototype } = Object,
{ ownKeys } = Reflect,
IteratorSymbol = Symbol.iterator;

// Type of `fix` function.
// `fix` can return a single fix, an array of fixes, or any iterator that yields fixes.
// e.g. `(function*() { yield fix1; yield fix2; })()`
Expand Down Expand Up @@ -101,13 +96,13 @@ export function getFixes(diagnostic: Diagnostic, ruleDetails: RuleDetails): Fix[
if (!fixes) return null;

// `fixes` can be any iterator, not just an array e.g. `fix: function*() { yield fix1; yield fix2; }`
if (IteratorSymbol in fixes) {
if (Symbol.iterator in fixes) {
let isCloned = false;

// Check prototype instead of using `Array.isArray()`, to ensure it is a native `Array`,
// not a subclass which may have overridden `toJSON()` in a way which could make `JSON.stringify()` throw
if (getPrototypeOf(fixes) !== ArrayPrototype || hasOwn(fixes, "toJSON")) {
fixes = ArrayFrom(fixes);
if (Object.getPrototypeOf(fixes) !== Array.prototype || Object.hasOwn(fixes, "toJSON")) {
fixes = Array.from(fixes);
isCloned = true;
}

Expand Down Expand Up @@ -176,12 +171,12 @@ function validateAndConformFix(fix: unknown): Fix {

// If `fix` is already well-formed, return it as-is.
// Note: `ownKeys(fix).length === 2` rules out `fix` having a custom `toJSON` method.
const fixPrototype = getPrototypeOf(fix);
const fixPrototype = Object.getPrototypeOf(fix);
if (
(fixPrototype === ObjectPrototype || fixPrototype === null) &&
ownKeys(fix).length === 2 &&
getPrototypeOf(range) === ArrayPrototype &&
!hasOwn(range, "toJSON") &&
(fixPrototype === Object.prototype || fixPrototype === null) &&
Reflect.ownKeys(fix).length === 2 &&
Object.getPrototypeOf(range) === Array.prototype &&
!Object.hasOwn(range, "toJSON") &&
range.length === 2 &&
typeof text === "string"
) {
Expand Down
7 changes: 2 additions & 5 deletions apps/oxlint/src-js/plugins/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

import { debugAssert, debugAssertIsNonNull } from "../utils/asserts.ts";

const { freeze } = Object,
{ isArray } = Array;

/**
* Globals for the file being linted.
*
Expand Down Expand Up @@ -55,11 +52,11 @@ export function initGlobals(): void {

// Freeze the globals object, to prevent any mutation of `globals` by plugins.
// No need to deep freeze since all keys are just strings.
freeze(globals);
Object.freeze(globals);
}

debugAssertIsNonNull(globals);
debugAssert(typeof globals === "object" && !isArray(globals));
debugAssert(typeof globals === "object" && !Array.isArray(globals));
}

/**
Expand Down
Loading
Loading