style(all): apply oxfmt default options (#16091)

Embrace the defaults!

---------

Co-authored-by: Yuji Sugiura <y.sugiura.0316@gmail.com>
This commit is contained in:
BoshenandYuji Sugiura authored and GitHub committed 2025-11-25 15:56:24 +09:00
1 parent 41ddbf5b55
commit 523a30a016
209 files changed
+8139 -7252

No files matched your search

+21 -19
View File
@@ -7,19 +7,19 @@
* Uses cargo tree to determine dependencies of oxc_coverage crate.
*/
const { getChangedFiles } = require('./get-changed-files.js');
const { getCrateDependencies } = require('./utils.js');
const { getChangedFiles } = require("./get-changed-files.js");
const { getCrateDependencies } = require("./utils.js");
/**
* Get all crates that conformance tests depend on
* @returns {string[]} Array of crate names
*/
function getCoverageDependencies() {
const packages = ['oxc_coverage', 'oxc_transform_conformance', 'oxc_prettier_conformance'];
const packages = ["oxc_coverage", "oxc_transform_conformance", "oxc_prettier_conformance"];
const deps = getCrateDependencies(packages);
console.error(`Conformance dependencies (${deps.length}):`);
console.error(` ${deps.join(', ')}`);
console.error(` ${deps.join(", ")}`);
return deps;
}
@@ -28,11 +28,11 @@ function getCoverageDependencies() {
* Directories that should always trigger conformance tests
*/
const ALWAYS_RUN_PATHS = [
'tasks/coverage/',
'tasks/common/',
'tasks/oxc_transform_conformance/',
'tasks/oxc_prettier_conformance/',
'pnpm-lock.yaml',
"tasks/coverage/",
"tasks/common/",
"tasks/oxc_transform_conformance/",
"tasks/oxc_prettier_conformance/",
"pnpm-lock.yaml",
];
/**
@@ -43,13 +43,13 @@ const ALWAYS_RUN_PATHS = [
function shouldRunConformance(changedFiles) {
// null means manual trigger or error - always run
if (changedFiles === null) {
console.error('No changed files list (manual trigger or error) - will run conformance');
console.error("No changed files list (manual trigger or error) - will run conformance");
return true;
}
// No files changed - skip conformance
if (changedFiles.length === 0) {
console.error('No files changed - will skip conformance');
console.error("No files changed - will skip conformance");
return false;
}
@@ -67,7 +67,7 @@ function shouldRunConformance(changedFiles) {
const dependencies = getCoverageDependencies();
if (dependencies.length === 0) {
console.error('Warning: No dependencies found - will run conformance as fallback');
console.error("Warning: No dependencies found - will run conformance as fallback");
return true;
}
@@ -82,7 +82,7 @@ function shouldRunConformance(changedFiles) {
}
}
console.error('No files affect oxc_coverage dependencies - will skip conformance');
console.error("No files affect oxc_coverage dependencies - will skip conformance");
return false;
}
@@ -95,21 +95,23 @@ async function main() {
const shouldRun = shouldRunConformance(changedFiles);
// Output for GitHub Actions
console.log(shouldRun ? 'true' : 'false');
console.log(shouldRun ? "true" : "false");
// Set GitHub Actions notice
if (shouldRun) {
console.error('::notice title=Conformance tests::Will run conformance tests');
console.error("::notice title=Conformance tests::Will run conformance tests");
} else {
console.error('::notice title=Conformance tests::Will skip conformance tests');
console.error("::notice title=Conformance tests::Will skip conformance tests");
}
process.exit(0);
} catch (error) {
console.error('Error checking conformance changes:', error);
console.error("Error checking conformance changes:", error);
// On error, run conformance as a fallback
console.log('true');
console.error('::warning title=Conformance check error::Error occurred, running conformance as fallback');
console.log("true");
console.error(
"::warning title=Conformance check error::Error occurred, running conformance as fallback",
);
process.exit(0);
}
}
+37 -20
View File
@@ -7,19 +7,28 @@
* This script determines which benchmark components need to run based on changed files.
*/
const process = require('process');
const { getChangedFiles } = require('./get-changed-files.js');
const { getCrateDependencies } = require('./utils.js');
const process = require("process");
const { getChangedFiles } = require("./get-changed-files.js");
const { getCrateDependencies } = require("./utils.js");
// All available benchmark components
const ALL_COMPONENTS = ['lexer', 'parser', 'transformer', 'semantic', 'minifier', 'codegen', 'formatter', 'linter'];
const ALL_COMPONENTS = [
"lexer",
"parser",
"transformer",
"semantic",
"minifier",
"codegen",
"formatter",
"linter",
];
// Files that when changed affect all benchmarks
const GLOBAL_FILES = [
'Cargo.lock',
'rust-toolchain.toml',
'.github/workflows/benchmark.yml',
'.github/scripts/generate-benchmark-matrix.js',
"Cargo.lock",
"rust-toolchain.toml",
".github/workflows/benchmark.yml",
".github/scripts/generate-benchmark-matrix.js",
];
/**
@@ -48,10 +57,10 @@ function checkGlobalChanges(changedFiles) {
* @returns {string} Feature name
*/
function getFeatureForComponent(component) {
if (component === 'linter') {
return 'linter';
if (component === "linter") {
return "linter";
}
return 'compiler';
return "compiler";
}
/**
@@ -61,7 +70,7 @@ function getFeatureForComponent(component) {
*/
function getComponentDependencies(component) {
const feature = getFeatureForComponent(component);
const deps = getCrateDependencies('oxc_benchmark', {
const deps = getCrateDependencies("oxc_benchmark", {
features: feature,
noDefaultFeatures: true,
});
@@ -86,7 +95,7 @@ function isComponentAffected(component, changedFiles) {
// Get component dependencies
const dependencies = getComponentDependencies(component);
console.error(`Component ${component} dependencies: ${dependencies.join(', ')}`);
console.error(`Component ${component} dependencies: ${dependencies.join(", ")}`);
// Check if any dependency files changed
for (const dep of dependencies) {
@@ -98,7 +107,11 @@ function isComponentAffected(component, changedFiles) {
}
// Check benchmark and common task files
if (changedFiles.some((file) => file.startsWith('tasks/benchmark/') || file.startsWith('tasks/common/'))) {
if (
changedFiles.some(
(file) => file.startsWith("tasks/benchmark/") || file.startsWith("tasks/common/"),
)
) {
console.error(` Component ${component} affected by benchmark/common file changes`);
return true;
}
@@ -123,7 +136,7 @@ async function determineAffectedComponents() {
// Check for global changes
if (checkGlobalChanges(changedFiles)) {
console.error('Global changes detected - will run all benchmarks');
console.error("Global changes detected - will run all benchmarks");
return ALL_COMPONENTS.map((component) => ({
component,
feature: getFeatureForComponent(component),
@@ -144,9 +157,11 @@ async function determineAffectedComponents() {
}
if (affectedComponents.length === 0) {
console.error('\nNo components were affected by the changes');
console.error("\nNo components were affected by the changes");
} else {
console.error(`\nAffected components: ${affectedComponents.map((obj) => obj.component).join(', ')}`);
console.error(
`\nAffected components: ${affectedComponents.map((obj) => obj.component).join(", ")}`,
);
}
return affectedComponents;
@@ -165,15 +180,17 @@ async function main() {
// Set GitHub Actions notice
if (affectedComponents.length === 0) {
console.error('::notice title=No benchmarks to run::No components were affected by the changes');
console.error(
"::notice title=No benchmarks to run::No components were affected by the changes",
);
} else {
const componentNames = affectedComponents.map((obj) => obj.component).join(', ');
const componentNames = affectedComponents.map((obj) => obj.component).join(", ");
console.error(`::notice title=Running benchmarks::Affected components: ${componentNames}`);
}
process.exit(0);
} catch (error) {
console.error('Error generating benchmark matrix:', error);
console.error("Error generating benchmark matrix:", error);
// On error, run all benchmarks as a fallback
const fallbackMatrix = ALL_COMPONENTS.map((component) => ({
component,
+17 -17
View File
@@ -7,8 +7,8 @@
* This module provides a reusable function for detecting changed files.
*/
const https = require('https');
const process = require('process');
const https = require("https");
const process = require("process");
/**
* Make a GitHub API request
@@ -18,25 +18,25 @@ const process = require('process');
function githubApi(path) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'api.github.com',
hostname: "api.github.com",
path,
headers: {
'User-Agent': 'oxc-changed-files',
Accept: 'application/vnd.github.v3+json',
"User-Agent": "oxc-changed-files",
Accept: "application/vnd.github.v3+json",
},
};
// Add authorization if token is available
const token = process.env.GITHUB_TOKEN;
if (token) {
options.headers['Authorization'] = `token ${token}`;
options.headers["Authorization"] = `token ${token}`;
}
https
.get(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
if (res.statusCode === 200) {
resolve(JSON.parse(data));
} else {
@@ -44,7 +44,7 @@ function githubApi(path) {
}
});
})
.on('error', reject);
.on("error", reject);
});
}
@@ -64,15 +64,15 @@ async function getChangedFiles() {
console.error(`SHA: ${sha}`);
console.error(`Ref: ${ref}`);
if (eventName === 'workflow_dispatch') {
console.error('Manual trigger - returning null (run all)');
if (eventName === "workflow_dispatch") {
console.error("Manual trigger - returning null (run all)");
return null; // Signal to run all
}
let files = [];
try {
if (eventName === 'pull_request' && prNumber) {
if (eventName === "pull_request" && prNumber) {
// For PR, use GitHub API to get changed files
console.error(`Getting changed files for PR #${prNumber}`);
const prFiles = await githubApi(`/repos/${repository}/pulls/${prNumber}/files?per_page=100`);
@@ -84,13 +84,13 @@ async function getChangedFiles() {
files = commit.files ? commit.files.map((f) => f.filename) : [];
} else {
// No valid parameters for API calls
console.error('Error: Missing required environment variables for GitHub API');
console.error('Returning null (run all) as fallback');
console.error("Error: Missing required environment variables for GitHub API");
console.error("Returning null (run all) as fallback");
return null; // Signal to run all
}
} catch (error) {
console.error(`Error getting changed files via API: ${error.message}`);
console.error('Returning null (run all) as fallback');
console.error("Returning null (run all) as fallback");
return null; // Signal to run all
}
@@ -110,7 +110,7 @@ if (require.main === module) {
process.exit(0);
})
.catch((error) => {
console.error('Error:', error);
console.error("Error:", error);
console.log(JSON.stringify(null));
process.exit(1);
});
+8 -8
View File
@@ -4,7 +4,7 @@
// oxlint-disable no-console
const { execSync } = require('child_process');
const { execSync } = require("child_process");
/**
* Execute a shell command and return the output
@@ -13,11 +13,11 @@ const { execSync } = require('child_process');
*/
function exec(command) {
try {
return execSync(command, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
return execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
} catch (error) {
console.error(`Error executing command: ${command}`);
console.error(error.message);
return '';
return "";
}
}
@@ -31,7 +31,7 @@ function exec(command) {
*/
function getCrateDependencies(packages, options = {}) {
const pkgs = Array.isArray(packages) ? packages : [packages];
const packageArgs = pkgs.map((pkg) => `-p ${pkg}`).join(' ');
const packageArgs = pkgs.map((pkg) => `-p ${pkg}`).join(" ");
let command = `cargo tree ${packageArgs} -f "{lib}" -e normal --no-dedupe --prefix none`;
@@ -40,20 +40,20 @@ function getCrateDependencies(packages, options = {}) {
}
if (options.noDefaultFeatures) {
command += ' --no-default-features';
command += " --no-default-features";
}
command += ' 2>/dev/null | grep oxc | sort -u';
command += " 2>/dev/null | grep oxc | sort -u";
const output = exec(command);
if (!output) {
console.error(`Warning: Could not get dependencies for ${pkgs.join(', ')}`);
console.error(`Warning: Could not get dependencies for ${pkgs.join(", ")}`);
return [];
}
// Filter out the queried packages themselves
return output.split('\n').filter((dep) => dep && !pkgs.includes(dep));
return output.split("\n").filter((dep) => dep && !pkgs.includes(dep));
}
/**
+18 -18
View File
@@ -1,35 +1,35 @@
// oxlint-disable no-console
import { execSync } from 'node:child_process';
import { copyFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { execSync } from "node:child_process";
import { copyFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const oxfmtDirPath = join(import.meta.dirname, '..'),
distDirPath = join(oxfmtDirPath, 'dist');
const oxfmtDirPath = join(import.meta.dirname, ".."),
distDirPath = join(oxfmtDirPath, "dist");
// Modify `bindings.js` to use correct package names
console.log('Modifying bindings.js...');
const bindingsPath = join(oxfmtDirPath, 'src-js/bindings.js');
let bindingsJs = readFileSync(bindingsPath, 'utf8');
console.log("Modifying bindings.js...");
const bindingsPath = join(oxfmtDirPath, "src-js/bindings.js");
let bindingsJs = readFileSync(bindingsPath, "utf8");
bindingsJs = bindingsJs.replace(/require\('@oxfmt\/binding-(.+?)'\)/g, (_, name) => {
name = name.replace(/-msvc(\/|$)/g, '$1');
name = name.replace(/-msvc(\/|$)/g, "$1");
return `require('@oxfmt/${name}')`;
});
writeFileSync(bindingsPath, bindingsJs);
// Build with tsdown
console.log('Building with tsdown...');
execSync('pnpm tsdown', { stdio: 'inherit', cwd: oxfmtDirPath });
console.log("Building with tsdown...");
execSync("pnpm tsdown", { stdio: "inherit", cwd: oxfmtDirPath });
// Copy native `.node` files from `src-js`
console.log('Copying `.node` files...');
console.log("Copying `.node` files...");
for (const filename of readdirSync(join(oxfmtDirPath, 'src-js'))) {
if (!filename.endsWith('.node')) continue;
copyFile(join(oxfmtDirPath, 'src-js', filename), join(distDirPath, filename));
for (const filename of readdirSync(join(oxfmtDirPath, "src-js"))) {
if (!filename.endsWith(".node")) continue;
copyFile(join(oxfmtDirPath, "src-js", filename), join(distDirPath, filename));
}
console.log('Build complete!');
console.log("Build complete!");
/**
* Copy a file, creating parent directories if needed.
@@ -38,7 +38,7 @@ console.log('Build complete!');
* @returns {void}
*/
function copyFile(srcPath, destPath) {
mkdirSync(join(destPath, '..'), { recursive: true });
mkdirSync(join(destPath, ".."), { recursive: true });
copyFileSync(srcPath, destPath);
console.log(`- Copied ${srcPath.split('/').pop()}`);
console.log(`- Copied ${srcPath.split("/").pop()}`);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { format } from './bindings.js';
import { formatEmbeddedCode } from './embedded.js';
import { format } from "./bindings.js";
import { formatEmbeddedCode } from "./embedded.js";
const args = process.argv.slice(2);
+9 -9
View File
@@ -9,24 +9,24 @@
// ```
// Yes, this seems completely fine!
// But actually, this makes `oxfmt --lsp` immediately stop with `Parse error` JSON-RPC error
let prettierCache: typeof import('prettier');
let prettierCache: typeof import("prettier");
// Map template tag names to Prettier parsers
const TAG_TO_PARSER: Record<string, string> = {
// CSS
css: 'css',
styled: 'css',
css: "css",
styled: "css",
// GraphQL
gql: 'graphql',
graphql: 'graphql',
gql: "graphql",
graphql: "graphql",
// HTML
html: 'html',
html: "html",
// Markdown
md: 'markdown',
markdown: 'markdown',
md: "markdown",
markdown: "markdown",
};
/**
@@ -45,7 +45,7 @@ export async function formatEmbeddedCode(tagName: string, code: string): Promise
}
if (!prettierCache) {
prettierCache = await import('prettier');
prettierCache = await import("prettier");
}
return prettierCache
+2 -2
View File
@@ -1,2 +1,2 @@
export * from './bindings.js';
export { formatEmbeddedCode } from './embedded.js';
export * from "./bindings.js";
export { formatEmbeddedCode } from "./embedded.js";
+15 -15
View File
@@ -1,30 +1,30 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('config_file', () => {
it('auto discovery', async () => {
describe("config_file", () => {
it("auto discovery", async () => {
const testCases = [
{ name: 'root', cwd: join(fixturesDir, 'config_file') },
{ name: 'nested', cwd: join(fixturesDir, 'config_file', 'nested') },
{ name: 'nested_deep', cwd: join(fixturesDir, 'config_file', 'nested', 'deep') },
{ name: "root", cwd: join(fixturesDir, "config_file") },
{ name: "nested", cwd: join(fixturesDir, "config_file", "nested") },
{ name: "nested_deep", cwd: join(fixturesDir, "config_file", "nested", "deep") },
];
for (const { name, cwd } of testCases) {
// oxlint-disable no-await-in-loop
const snapshot = await runAndSnapshot(cwd, [['--check']]);
const snapshot = await runAndSnapshot(cwd, [["--check"]]);
expect(snapshot).toMatchSnapshot(name);
}
});
it('explicit config', async () => {
const cwd = join(fixturesDir, 'config_file');
it("explicit config", async () => {
const cwd = join(fixturesDir, "config_file");
const testCases = [
['--check', '--config', './fmt.json'],
['--check', '--config', './fmt.jsonc'],
['--check', '--config', 'NOT_EXISTS.json'],
["--check", "--config", "./fmt.json"],
["--check", "--config", "./fmt.jsonc"],
["--check", "--config", "NOT_EXISTS.json"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
@@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('config_ignore_patterns', () => {
it('should respect ignorePatterns in config', async () => {
const cwd = join(fixturesDir, 'config_ignore_patterns');
const testCases = [['--check'], ['--check', '--config', 'fmtrc.jsonc']];
describe("config_ignore_patterns", () => {
it("should respect ignorePatterns in config", async () => {
const cwd = join(fixturesDir, "config_ignore_patterns");
const testCases = [["--check"], ["--check", "--config", "fmtrc.jsonc"]];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
+13 -13
View File
@@ -1,30 +1,30 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runWriteModeAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runWriteModeAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures', 'embedded_languages');
const fixturesDir = join(__dirname, "fixtures", "embedded_languages");
describe('embedded_languages', () => {
it('should format embedded languages (CSS, GraphQL, HTML, Markdown)', async () => {
describe("embedded_languages", () => {
it("should format embedded languages (CSS, GraphQL, HTML, Markdown)", async () => {
const snapshot = await runWriteModeAndSnapshot(
fixturesDir,
['embedded_languages.js'],
['--config', 'on_embedded.json'],
["embedded_languages.js"],
["--config", "on_embedded.json"],
);
expect(snapshot).toMatchSnapshot();
});
it('should not format embedded languages when embeddedLanguageFormatting is off', async () => {
it("should not format embedded languages when embeddedLanguageFormatting is off", async () => {
const snapshot = await runWriteModeAndSnapshot(
fixturesDir,
['embedded_languages.js'],
['--config', 'off_embedded.json'],
["embedded_languages.js"],
["--config", "off_embedded.json"],
);
expect(snapshot).toMatchSnapshot();
});
it('should not format embedded languages by default (at alpha release)', async () => {
const snapshot = await runWriteModeAndSnapshot(fixturesDir, ['embedded_languages.js']);
it("should not format embedded languages by default (at alpha release)", async () => {
const snapshot = await runWriteModeAndSnapshot(fixturesDir, ["embedded_languages.js"]);
expect(snapshot).toMatchSnapshot();
});
});
+28 -28
View File
@@ -1,48 +1,48 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('exclude_nested', () => {
it('should correctly exclude nested paths', async () => {
const cwd = join(fixturesDir, 'exclude_nested');
describe("exclude_nested", () => {
it("should correctly exclude nested paths", async () => {
const cwd = join(fixturesDir, "exclude_nested");
const testCases = [
['--check', '!foo/bar/error.js'],
['--check', '!foo/bar'],
['--check', '!foo'],
['--check', '!**/error.js'],
['--check', 'foo', '!foo/bar/error.js'],
['--check', 'foo', '!foo/bar'],
['--check', 'foo', '!**/bar/error.js'],
['--check', 'foo', '!**/bar/*'],
["--check", "!foo/bar/error.js"],
["--check", "!foo/bar"],
["--check", "!foo"],
["--check", "!**/error.js"],
["--check", "foo", "!foo/bar/error.js"],
["--check", "foo", "!foo/bar"],
["--check", "foo", "!**/bar/error.js"],
["--check", "foo", "!**/bar/*"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
});
it('should correctly exclude nested paths with dot - set 1', async () => {
const cwd = join(fixturesDir, 'exclude_nested');
it("should correctly exclude nested paths with dot - set 1", async () => {
const cwd = join(fixturesDir, "exclude_nested");
const testCases = [
['--check', '.', '!foo/bar/error.js'],
['--check', '.', '!foo/bar'],
['--check', '.', '!foo'],
['--check', '.', '!**/error.js'],
["--check", ".", "!foo/bar/error.js"],
["--check", ".", "!foo/bar"],
["--check", ".", "!foo"],
["--check", ".", "!**/error.js"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
});
it('should correctly exclude nested paths with dot - set 2', async () => {
const cwd = join(fixturesDir, 'exclude_nested');
it("should correctly exclude nested paths with dot - set 2", async () => {
const cwd = join(fixturesDir, "exclude_nested");
const testCases = [
['--check', './foo', '!**/bar/error.js'],
['--check', './foo', '!**/error.js'],
['--check', './foo', '!**/bar/*'],
['--check', './foo', '!foo/bar/error.js'],
['--check', './foo', '!foo/bar'],
["--check", "./foo", "!**/bar/error.js"],
["--check", "./foo", "!**/error.js"],
["--check", "./foo", "!**/bar/*"],
["--check", "./foo", "!foo/bar/error.js"],
["--check", "./foo", "!foo/bar"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
+8 -8
View File
@@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('extensions', () => {
it('should check files with supported extensions', async () => {
const cwd = join(fixturesDir, 'extensions');
const testCases = [['--check']];
describe("extensions", () => {
it("should check files with supported extensions", async () => {
const cwd = join(fixturesDir, "extensions");
const testCases = [["--check"]];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
+11 -11
View File
@@ -1,17 +1,17 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('ignore_and_override', () => {
it('should handle ignore files with overrides', async () => {
const cwd = join(fixturesDir, 'ignore_and_override');
describe("ignore_and_override", () => {
it("should handle ignore files with overrides", async () => {
const cwd = join(fixturesDir, "ignore_and_override");
const testCases = [
['--check', '!**/err.js'],
['--check', '--ignore-path', 'ignore1'],
['--check', '--ignore-path', 'ignore1', 'should_format/ok.js'],
['--check', '--ignore-path', 'ignore2'],
["--check", "!**/err.js"],
["--check", "--ignore-path", "ignore1"],
["--check", "--ignore-path", "ignore1", "should_format/ok.js"],
["--check", "--ignore-path", "ignore2"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
+11 -11
View File
@@ -1,17 +1,17 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('ignore_patterns', () => {
it('should handle ignore files correctly', async () => {
const cwd = join(fixturesDir, 'ignore_patterns');
describe("ignore_patterns", () => {
it("should handle ignore files correctly", async () => {
const cwd = join(fixturesDir, "ignore_patterns");
const testCases = [
['--check'],
['--check', '--ignore-path', 'gitignore.txt'],
['--check', '--ignore-path', 'gitignore.txt', '--ignore-path', '.prettierignore'],
['--check', '--ignore-path', 'nonexistent.ignore'],
["--check"],
["--check", "--ignore-path", "gitignore.txt"],
["--check", "--ignore-path", "gitignore.txt", "--ignore-path", ".prettierignore"],
["--check", "--ignore-path", "nonexistent.ignore"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
+8 -8
View File
@@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('ignore_symlink', () => {
it('should ignore symlinks by default', async () => {
const cwd = join(fixturesDir, 'symlink_dirs');
const testCases = [['--check']];
describe("ignore_symlink", () => {
it("should ignore symlinks by default", async () => {
const cwd = join(fixturesDir, "symlink_dirs");
const testCases = [["--check"]];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
+12 -12
View File
@@ -1,18 +1,18 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('multiple_files', () => {
it('should handle multiple files with different patterns', async () => {
const cwd = join(fixturesDir, 'multiple_files');
describe("multiple_files", () => {
it("should handle multiple files with different patterns", async () => {
const cwd = join(fixturesDir, "multiple_files");
const testCases = [
['--check', 'simple.js', 'arrow.js'],
['--check'],
['--check', '.'],
['--check', './'],
['--check', '!*.{ts,tsx}'],
["--check", "simple.js", "arrow.js"],
["--check"],
["--check", "."],
["--check", "./"],
["--check", "!*.{ts,tsx}"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
@@ -1,15 +1,15 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('no_error_on_unmatched_pattern', () => {
it('should handle --no-error-on-unmatched-pattern flag', async () => {
describe("no_error_on_unmatched_pattern", () => {
it("should handle --no-error-on-unmatched-pattern flag", async () => {
const cwd = fixturesDir;
const testCases = [
['--check', '--no-error-on-unmatched-pattern', '__non__existent__file.js'],
['--check', '__non__existent__file.js'],
["--check", "--no-error-on-unmatched-pattern", "__non__existent__file.js"],
["--check", "__non__existent__file.js"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
+8 -8
View File
@@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('node_modules_dirs', () => {
it('should ignore node_modules by default but include with flag', async () => {
const cwd = join(fixturesDir, 'node_modules_dirs');
const testCases = [['--check'], ['--check', '--with-node-modules']];
describe("node_modules_dirs", () => {
it("should ignore node_modules by default but include with flag", async () => {
const cwd = join(fixturesDir, "node_modules_dirs");
const testCases = [["--check"], ["--check", "--with-node-modules"]];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
+9 -9
View File
@@ -1,15 +1,15 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('single_file', () => {
it('should handle single file with different flags', async () => {
const cwd = join(fixturesDir, 'single_file');
describe("single_file", () => {
it("should handle single file with different flags", async () => {
const cwd = join(fixturesDir, "single_file");
const testCases = [
['--check', 'simple.js'],
['--list-different', 'simple.js'],
["--check", "simple.js"],
["--list-different", "simple.js"],
];
const snapshot = await runAndSnapshot(cwd, testCases);
+26 -22
View File
@@ -1,8 +1,8 @@
// oxlint-disable no-await-in-loop
import { join, relative } from 'node:path';
import fs from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { execa } from 'execa';
import { join, relative } from "node:path";
import fs from "node:fs/promises";
import { tmpdir } from "node:os";
import { execa } from "execa";
// NOTE: TS's ESNext is not yet reached to ES2025...
declare global {
@@ -18,7 +18,7 @@ export async function runAndSnapshot(cwd: string, testCases: string[][]): Promis
const result = await runCli(cwd, args);
snapshot.push(formatSnapshot(cwd, args, result));
}
return normalizeOutput(snapshot.join('\n'), cwd);
return normalizeOutput(snapshot.join("\n"), cwd);
}
// Test function for write mode
@@ -27,7 +27,7 @@ export async function runWriteModeAndSnapshot(
files: string[],
args: string[] = [],
): Promise<string> {
const tempDir = await fs.mkdtemp(join(tmpdir(), 'oxfmt-test-'));
const tempDir = await fs.mkdtemp(join(tmpdir(), "oxfmt-test-"));
try {
await fs.cp(fixtureDir, tempDir, { recursive: true });
@@ -36,10 +36,10 @@ export async function runWriteModeAndSnapshot(
for (const file of files) {
const filePath = join(tempDir, file);
const beforeContent = await fs.readFile(filePath, 'utf8');
const beforeContent = await fs.readFile(filePath, "utf8");
await runCli(tempDir, [...args, file]); // Write by default
const afterContent = await fs.readFile(filePath, 'utf8');
const afterContent = await fs.readFile(filePath, "utf8");
snapshot.push(
`
@@ -54,7 +54,7 @@ ${afterContent}
);
}
return snapshot.join('\n\n');
return snapshot.join("\n\n");
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
@@ -69,9 +69,9 @@ type RunResult = {
};
async function runCli(cwd: string, args: string[]): Promise<RunResult> {
const cliPath = join(import.meta.dirname, '..', 'dist', 'cli.js');
const cliPath = join(import.meta.dirname, "..", "dist", "cli.js");
const result = await execa('node', [cliPath, ...args], {
const result = await execa("node", [cliPath, ...args], {
cwd,
reject: false,
});
@@ -87,29 +87,33 @@ function normalizeOutput(output: string, cwd: string): string {
let normalized = output;
// Normalize timing information
normalized = normalized.replace(/\d+(?:\.\d+)?s|\d+ms/g, '<variable>ms');
normalized = normalized.replace(/\d+(?:\.\d+)?s|\d+ms/g, "<variable>ms");
// Normalize thread count (e.g., "using 8 threads" -> "using 1 threads")
normalized = normalized.replace(/using \d+ threads/g, 'using 1 threads');
normalized = normalized.replace(/using \d+ threads/g, "using 1 threads");
// Normalize path separators (Windows compatibility)
normalized = normalized.replace(/\\/g, '/');
normalized = normalized.replace(/\\/g, "/");
// Replace absolute paths
const cwdPath = cwd.replace(/\\/g, '/');
normalized = normalized.replace(new RegExp(RegExp.escape(cwdPath), 'g'), '<cwd>');
const cwdPath = cwd.replace(/\\/g, "/");
normalized = normalized.replace(new RegExp(RegExp.escape(cwdPath), "g"), "<cwd>");
// Replace repo root path
const repoRoot = join(import.meta.dirname, '..', '..', '..');
const rootPath = repoRoot.replace(/\\/g, '/');
normalized = normalized.replace(new RegExp(RegExp.escape(rootPath), 'g'), '<cwd>');
const repoRoot = join(import.meta.dirname, "..", "..", "..");
const rootPath = repoRoot.replace(/\\/g, "/");
normalized = normalized.replace(new RegExp(RegExp.escape(rootPath), "g"), "<cwd>");
return normalized;
}
function formatSnapshot(cwd: string, args: string[], { stdout, stderr, exitCode }: RunResult): string {
function formatSnapshot(
cwd: string,
args: string[],
{ stdout, stderr, exitCode }: RunResult,
): string {
const testsRoot = join(import.meta.dirname);
const relativeDir = relative(testsRoot, cwd) || '.';
const relativeDir = relative(testsRoot, cwd) || ".";
return `
--------------------
arguments: ${args.join(' ')}
arguments: ${args.join(" ")}
working directory: ${relativeDir}
exit code: ${exitCode}
--- STDOUT ---------
+8 -8
View File
@@ -1,13 +1,13 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('vcs_dirs', () => {
it('should ignore VCS directories by default', async () => {
const cwd = join(fixturesDir, 'vcs_dirs');
const testCases = [['--check']];
describe("vcs_dirs", () => {
it("should ignore VCS directories by default", async () => {
const cwd = join(fixturesDir, "vcs_dirs");
const testCases = [["--check"]];
const snapshot = await runAndSnapshot(cwd, testCases);
expect(snapshot).toMatchSnapshot();
+11 -11
View File
@@ -1,21 +1,21 @@
import { describe, expect, it } from 'vitest';
import { join } from 'node:path';
import { runWriteModeAndSnapshot } from './utils';
import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { runWriteModeAndSnapshot } from "./utils";
const fixturesDir = join(__dirname, 'fixtures');
const fixturesDir = join(__dirname, "fixtures");
describe('write_mode', () => {
it('should format unformatted files', async () => {
const fixtureDir = join(fixturesDir, 'write_mode');
const files = ['unformatted.js', 'complex.js'];
describe("write_mode", () => {
it("should format unformatted files", async () => {
const fixtureDir = join(fixturesDir, "write_mode");
const files = ["unformatted.js", "complex.js"];
const snapshot = await runWriteModeAndSnapshot(fixtureDir, files);
expect(snapshot).toMatchSnapshot();
});
it('should not modify already formatted files', async () => {
const fixtureDir = join(fixturesDir, 'write_mode');
const files = ['already_formatted.js'];
it("should not modify already formatted files", async () => {
const fixtureDir = join(fixturesDir, "write_mode");
const files = ["already_formatted.js"];
const snapshot = await runWriteModeAndSnapshot(fixtureDir, files);
expect(snapshot).toMatchSnapshot();
+7 -7
View File
@@ -1,14 +1,14 @@
import { defineConfig } from 'tsdown';
import { defineConfig } from "tsdown";
export default defineConfig({
entry: ['src-js/index.ts', 'src-js/cli.ts'],
format: 'esm',
platform: 'node',
target: 'node20',
entry: ["src-js/index.ts", "src-js/cli.ts"],
format: "esm",
platform: "node",
target: "node20",
dts: true,
clean: true,
outDir: 'dist',
outDir: "dist",
shims: false,
fixedExtension: false,
noExternal: ['prettier'],
noExternal: ["prettier"],
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { configDefaults, defineConfig } from 'vitest/config';
import { configDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
+17 -17
View File
@@ -1,37 +1,37 @@
// oxlint-disable no-console
import { execSync } from 'node:child_process';
import { copyFileSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { execSync } from "node:child_process";
import { copyFileSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const oxlintDirPath = join(import.meta.dirname, '..'),
srcDirPath = join(oxlintDirPath, 'src-js'),
distDirPath = join(oxlintDirPath, 'dist');
const oxlintDirPath = join(import.meta.dirname, ".."),
srcDirPath = join(oxlintDirPath, "src-js"),
distDirPath = join(oxlintDirPath, "dist");
// Modify `bindings.js` to use correct package names
console.log('Modifying bindings.js...');
const bindingsPath = join(oxlintDirPath, 'src-js/bindings.js');
let bindingsJs = readFileSync(bindingsPath, 'utf8');
console.log("Modifying bindings.js...");
const bindingsPath = join(oxlintDirPath, "src-js/bindings.js");
let bindingsJs = readFileSync(bindingsPath, "utf8");
bindingsJs = bindingsJs.replace(/require\('@oxlint\/binding-(.+?)'\)/g, (_, name) => {
name = name.replace(/-msvc(\/|$)/g, '$1');
name = name.replace(/-msvc(\/|$)/g, "$1");
return `require('@oxlint/${name}')`;
});
writeFileSync(bindingsPath, bindingsJs);
// Build with tsdown
console.log('Building with tsdown...');
execSync('pnpm tsdown', { stdio: 'inherit', cwd: oxlintDirPath });
console.log("Building with tsdown...");
execSync("pnpm tsdown", { stdio: "inherit", cwd: oxlintDirPath });
// Delete `cli.d.ts`
console.log('Deleting cli.d.ts...');
rmSync(join(distDirPath, 'cli.d.ts'));
console.log("Deleting cli.d.ts...");
rmSync(join(distDirPath, "cli.d.ts"));
// Copy native `.node` files from `src-js`
console.log('Copying `.node` files...');
console.log("Copying `.node` files...");
for (const filename of readdirSync(srcDirPath)) {
if (!filename.endsWith('.node')) continue;
if (!filename.endsWith(".node")) continue;
const srcPath = join(srcDirPath, filename);
copyFileSync(srcPath, join(distDirPath, filename));
}
console.log('Build complete!');
console.log("Build complete!");
+3 -3
View File
@@ -1,5 +1,5 @@
import { lint } from './bindings.js';
import { debugAssertIsNonNull } from './utils/asserts.js';
import { lint } from "./bindings.js";
import { debugAssertIsNonNull } from "./utils/asserts.js";
// Lazy-loaded JS plugin-related functions.
// Using `typeof wrapper` here makes TS check that the function signatures of `loadPlugin` and `loadPluginWrapper`
@@ -20,7 +20,7 @@ function loadPluginWrapper(path: string, packageName: string | null): Promise<st
if (loadPlugin === null) {
// Use promises here instead of making `loadPluginWrapper` an async function,
// to avoid a micro-tick and extra wrapper `Promise` in all later calls to `loadPluginWrapper`
return import('./plugins/index.js').then((mod) => {
return import("./plugins/index.js").then((mod) => {
({ loadPlugin, lintFile } = mod);
return loadPlugin(path, packageName);
});
+57 -45
View File
@@ -1,17 +1,17 @@
import { debugAssertIsNonNull } from './utils/asserts.js';
import { debugAssertIsNonNull } from "./utils/asserts.js";
import type { Context, FileContext, LanguageOptions } from './plugins/context.ts';
import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
import type { Settings } from './plugins/settings.ts';
import type { SourceCode } from './plugins/source_code.ts';
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';
import type { SetNullable } from './utils/types.ts';
import type { Context, FileContext, LanguageOptions } from "./plugins/context.ts";
import type { CreateOnceRule, Plugin, Rule } from "./plugins/load.ts";
import type { Settings } from "./plugins/settings.ts";
import type { SourceCode } from "./plugins/source_code.ts";
import type { BeforeHook, Visitor, VisitorWithHooks } from "./plugins/types.ts";
import type { SetNullable } from "./utils/types.ts";
export type * as ESTree from './generated/types.d.ts';
export type { Context, LanguageOptions } from './plugins/context.ts';
export type { Fix, Fixer, FixFn } from './plugins/fix.ts';
export type { CreateOnceRule, CreateRule, Options, Plugin, Rule } from './plugins/load.ts';
export type { Diagnostic, Suggestion } from './plugins/report.ts';
export type * as ESTree from "./generated/types.d.ts";
export type { Context, LanguageOptions } from "./plugins/context.ts";
export type { Fix, Fixer, FixFn } from "./plugins/fix.ts";
export type { CreateOnceRule, CreateRule, Options, Plugin, Rule } from "./plugins/load.ts";
export type { Diagnostic, Suggestion } from "./plugins/report.ts";
export type {
Definition,
DefinitionType,
@@ -20,9 +20,9 @@ export type {
ScopeManager,
ScopeType,
Variable,
} from './plugins/scope.ts';
export type { Settings } from './plugins/settings.ts';
export type { SourceCode } from './plugins/source_code.ts';
} from "./plugins/scope.ts";
export type { Settings } from "./plugins/settings.ts";
export type { SourceCode } from "./plugins/source_code.ts";
export type {
CountOptions,
FilterFn,
@@ -44,7 +44,7 @@ export type {
RegularExpressionToken,
StringToken,
TemplateToken,
} from './plugins/tokens.ts';
} from "./plugins/tokens.ts";
export type {
RuleMeta,
RuleDocs,
@@ -52,9 +52,17 @@ export type {
RuleDeprecatedInfo,
RuleReplacedByInfo,
RuleReplacedByExternalSpecifier,
} from './plugins/rule_meta.ts';
export type { LineColumn, Location, Range, Ranged, Span } from './plugins/location.ts';
export type { AfterHook, BeforeHook, Comment, Node, NodeOrToken, Visitor, VisitorWithHooks } from './plugins/types.ts';
} from "./plugins/rule_meta.ts";
export type { LineColumn, Location, Range, Ranged, Span } from "./plugins/location.ts";
export type {
AfterHook,
BeforeHook,
Comment,
Node,
NodeOrToken,
Visitor,
VisitorWithHooks,
} from "./plugins/types.ts";
const {
defineProperty,
@@ -84,10 +92,11 @@ const EMPTY_VISITOR: Visitor = {};
*/
export function definePlugin(plugin: Plugin): Plugin {
// Validate type of `plugin`
if (plugin === null || typeof plugin !== 'object') throw new Error('Plugin must be an object');
if (plugin === null || typeof plugin !== "object") throw new Error("Plugin must be an object");
const { rules } = plugin;
if (rules === null || typeof rules !== 'object') throw new Error('Plugin must have an object as `rules` property');
if (rules === null || typeof rules !== "object")
throw new Error("Plugin must have an object as `rules` property");
// Make each rule in the plugin ESLint-compatible by calling `defineRule` on it
for (const ruleName in rules) {
@@ -114,10 +123,10 @@ export function definePlugin(plugin: Plugin): Plugin {
*/
export function defineRule(rule: Rule): Rule {
// Validate type of `rule`
if (rule === null || typeof rule !== 'object') throw new Error('Rule must be an object');
if (rule === null || typeof rule !== "object") throw new Error("Rule must be an object");
// If rule already has `create` method, return it as is
if ('create' in rule) return rule;
if ("create" in rule) return rule;
// Add `create` function to `rule`
let context: Context | null = null,
@@ -134,9 +143,9 @@ 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 });
defineProperty(context, "id", { value: eslintContext.id });
defineProperty(context, "options", { value: eslintContext.options });
defineProperty(context, "report", { value: eslintContext.report });
setPrototypeOf(context, getPrototypeOf(eslintContext));
// If `before` hook returns `false`, skip traversal by returning an empty object as visitor
@@ -164,19 +173,19 @@ let cwd: string | null = null;
// See `FILE_CONTEXT` in `plugins/context.ts` for details of all the getters/methods.
const FILE_CONTEXT: FileContext = freeze({
get filename(): string {
throw new Error('Cannot access `context.filename` in `createOnce`');
throw new Error("Cannot access `context.filename` in `createOnce`");
},
getFilename(): string {
throw new Error('Cannot call `context.getFilename` in `createOnce`');
throw new Error("Cannot call `context.getFilename` in `createOnce`");
},
get physicalFilename(): string {
throw new Error('Cannot access `context.physicalFilename` in `createOnce`');
throw new Error("Cannot access `context.physicalFilename` in `createOnce`");
},
getPhysicalFilename(): string {
throw new Error('Cannot call `context.getPhysicalFilename` in `createOnce`');
throw new Error("Cannot call `context.getPhysicalFilename` in `createOnce`");
},
get cwd(): string {
@@ -191,19 +200,19 @@ const FILE_CONTEXT: FileContext = freeze({
},
get sourceCode(): SourceCode {
throw new Error('Cannot access `context.sourceCode` in `createOnce`');
throw new Error("Cannot access `context.sourceCode` in `createOnce`");
},
getSourceCode(): SourceCode {
throw new Error('Cannot call `context.getSourceCode` in `createOnce`');
throw new Error("Cannot call `context.getSourceCode` in `createOnce`");
},
get languageOptions(): LanguageOptions {
throw new Error('Cannot access `context.languageOptions` in `createOnce`');
throw new Error("Cannot access `context.languageOptions` in `createOnce`");
},
get settings(): Readonly<Settings> {
throw new Error('Cannot access `context.settings` in `createOnce`');
throw new Error("Cannot access `context.settings` in `createOnce`");
},
extend(this: FileContext, extension: Record<string | number | symbol, unknown>): FileContext {
@@ -212,11 +221,11 @@ const FILE_CONTEXT: FileContext = freeze({
},
get parserOptions(): Record<string, unknown> {
throw new Error('Cannot access `context.parserOptions` in `createOnce`');
throw new Error("Cannot access `context.parserOptions` in `createOnce`");
},
get parserPath(): string {
throw new Error('Cannot access `context.parserPath` in `createOnce`');
throw new Error("Cannot access `context.parserPath` in `createOnce`");
},
});
@@ -233,15 +242,17 @@ function createContextAndVisitor(rule: CreateOnceRule): {
} {
// Validate type of `createOnce`
const { createOnce } = rule;
if (createOnce == null) throw new Error('Rules must define either a `create` or `createOnce` method');
if (typeof createOnce !== 'function') throw new Error('Rule `createOnce` property must be a function');
if (createOnce == null)
throw new Error("Rules must define either a `create` or `createOnce` method");
if (typeof createOnce !== "function")
throw new Error("Rule `createOnce` property must be a function");
// Call `createOnce` with empty context object.
// 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, {
id: { value: '', enumerable: true, configurable: true },
id: { value: "", enumerable: true, configurable: true },
options: { value: null, enumerable: true, configurable: true },
report: { value: null, enumerable: true, configurable: true },
});
@@ -250,20 +261,21 @@ function createContextAndVisitor(rule: CreateOnceRule): {
before: beforeHook,
after: afterHook,
...visitor
} = createOnce.call(rule, context) as SetNullable<VisitorWithHooks, 'before' | 'after'>;
} = createOnce.call(rule, context) as SetNullable<VisitorWithHooks, "before" | "after">;
if (beforeHook === undefined) {
beforeHook = null;
} else if (beforeHook !== null && typeof beforeHook !== 'function') {
throw new Error('`before` property of visitor must be a function if defined');
} else if (beforeHook !== null && typeof beforeHook !== "function") {
throw new Error("`before` property of visitor must be a function if defined");
}
// Add `after` hook to `Program:exit` visit fn
if (afterHook != null) {
if (typeof afterHook !== 'function') throw new Error('`after` property of visitor must be a function if defined');
if (typeof afterHook !== "function")
throw new Error("`after` property of visitor must be a function if defined");
const programExit = visitor['Program:exit'];
visitor['Program:exit'] =
const programExit = visitor["Program:exit"];
visitor["Program:exit"] =
programExit == null
? (_node) => afterHook()
: (node) => {
+8 -5
View File
@@ -2,10 +2,10 @@
* `SourceCode` methods related to comments.
*/
import { ast, initAst, sourceText } from './source_code.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
import { ast, initAst, sourceText } from "./source_code.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { Comment, Node, NodeOrToken } from './types.ts';
import type { Comment, Node, NodeOrToken } from "./types.ts";
// Regex that tests if a string is entirely whitespace.
const WHITESPACE_ONLY_REGEXP = /^\s*$/;
@@ -184,7 +184,10 @@ export function getCommentsInside(node: Node): Comment[] {
* @param nodeOrToken2 - End node/token.
* @returns `true` if one or more comments exist between the two.
*/
export function commentsExistBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrToken): boolean {
export function commentsExistBetween(
nodeOrToken1: NodeOrToken,
nodeOrToken2: NodeOrToken,
): boolean {
if (ast === null) initAst();
debugAssertIsNonNull(ast);
@@ -220,6 +223,6 @@ export function commentsExistBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: No
*/
/* oxlint-disable no-unused-vars */
export function getJSDocComment(node: Node): Comment | null {
throw new Error('`sourceCode.getJSDocComment` is not supported at present (and deprecated)'); // TODO
throw new Error("`sourceCode.getJSDocComment` is not supported at present (and deprecated)"); // TODO
}
/* oxlint-enable no-unused-vars */
+26 -23
View File
@@ -26,16 +26,16 @@
* and global variables (`filePath`, `settings`, `cwd`).
*/
import { ast, initAst, SOURCE_CODE } from './source_code.js';
import { report } from './report.js';
import { settings, initSettings } from './settings.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
import { ast, initAst, SOURCE_CODE } from "./source_code.js";
import { report } from "./report.js";
import { settings, initSettings } from "./settings.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { Options, RuleDetails } from './load.ts';
import type { Diagnostic } from './report.ts';
import type { Settings } from './settings.ts';
import type { SourceCode } from './source_code.ts';
import type { ModuleKind } from '../generated/types.d.ts';
import type { Options, RuleDetails } from "./load.ts";
import type { Diagnostic } from "./report.ts";
import type { Settings } from "./settings.ts";
import type { SourceCode } from "./source_code.ts";
import type { ModuleKind } from "../generated/types.d.ts";
const { freeze, assign: ObjectAssign, create: ObjectCreate } = Object;
@@ -109,7 +109,7 @@ const LANGUAGE_OPTIONS = freeze({
* Parser used to parse the file being linted.
*/
get parser(): Record<string, unknown> {
throw new Error('`context.languageOptions.parser` not implemented yet.'); // TODO
throw new Error("`context.languageOptions.parser` not implemented yet."); // TODO
},
/**
@@ -122,7 +122,7 @@ const LANGUAGE_OPTIONS = freeze({
* Globals defined for the file being linted.
*/
// ESLint has `globals` as `null`, not empty object, if no globals are defined.
get globals(): Record<string, 'readonly' | 'writable' | 'off'> | null {
get globals(): Record<string, "readonly" | "writable" | "off"> | null {
// TODO: Get globals from Rust side.
// Note: ESLint's type is "writable", whereas Oxlint's is "writeable" (misspelled with extra "e").
// Probably we should fix that on Rust side (while still allowing "writeable").
@@ -168,7 +168,7 @@ const FILE_CONTEXT = freeze({
*/
get filename(): string {
// Note: If we change this implementation, also change `getFilename` method below
if (filePath === null) throw new Error('Cannot access `context.filename` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.filename` in `createOnce`");
return filePath;
},
@@ -178,7 +178,7 @@ const FILE_CONTEXT = freeze({
* @deprecated Use `context.filename` property instead.
*/
getFilename(): string {
if (filePath === null) throw new Error('Cannot call `context.getFilename` in `createOnce`');
if (filePath === null) throw new Error("Cannot call `context.getFilename` in `createOnce`");
return filePath;
},
@@ -188,7 +188,8 @@ const FILE_CONTEXT = freeze({
// TODO: Unclear how this differs from `filename`.
get physicalFilename(): string {
// Note: If we change this implementation, also change `getPhysicalFilename` method below
if (filePath === null) throw new Error('Cannot access `context.physicalFilename` in `createOnce`');
if (filePath === null)
throw new Error("Cannot access `context.physicalFilename` in `createOnce`");
return filePath;
},
@@ -198,7 +199,8 @@ const FILE_CONTEXT = freeze({
* @deprecated Use `context.physicalFilename` property instead.
*/
getPhysicalFilename(): string {
if (filePath === null) throw new Error('Cannot call `context.getPhysicalFilename` in `createOnce`');
if (filePath === null)
throw new Error("Cannot call `context.getPhysicalFilename` in `createOnce`");
return filePath;
},
@@ -228,7 +230,7 @@ const FILE_CONTEXT = freeze({
*/
get sourceCode(): SourceCode {
// Note: If we change this implementation, also change `getSourceCode` method below
if (filePath === null) throw new Error('Cannot access `context.sourceCode` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.sourceCode` in `createOnce`");
return SOURCE_CODE;
},
@@ -238,7 +240,7 @@ const FILE_CONTEXT = freeze({
* @deprecated Use `context.sourceCode` property instead.
*/
getSourceCode(): SourceCode {
if (filePath === null) throw new Error('Cannot call `context.getSourceCode` in `createOnce`');
if (filePath === null) throw new Error("Cannot call `context.getSourceCode` in `createOnce`");
return SOURCE_CODE;
},
@@ -246,7 +248,8 @@ const FILE_CONTEXT = freeze({
* Language options used when parsing this file.
*/
get languageOptions(): LanguageOptions {
if (filePath === null) throw new Error('Cannot access `context.languageOptions` in `createOnce`');
if (filePath === null)
throw new Error("Cannot access `context.languageOptions` in `createOnce`");
return LANGUAGE_OPTIONS;
},
@@ -254,7 +257,7 @@ const FILE_CONTEXT = freeze({
* Settings for the file being linted.
*/
get settings(): Readonly<Settings> {
if (filePath === null) throw new Error('Cannot access `context.settings` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.settings` in `createOnce`");
if (settings === null) initSettings();
debugAssertIsNonNull(settings);
@@ -279,7 +282,7 @@ const FILE_CONTEXT = freeze({
* @deprecated Use `languageOptions.parserOptions` instead.
*/
get parserOptions(): Record<string, unknown> {
if (filePath === null) throw new Error('Cannot access `context.parserOptions` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.parserOptions` in `createOnce`");
return PARSER_OPTIONS;
},
@@ -289,7 +292,7 @@ const FILE_CONTEXT = freeze({
*/
get parserPath(): string {
// TODO: Implement this?
throw new Error('`context.parserPath` is unsupported at present (and deprecated)');
throw new Error("`context.parserPath` is unsupported at present (and deprecated)");
},
});
@@ -354,12 +357,12 @@ export function createContext(fullRuleName: string, ruleDetails: RuleDetails): R
get id(): string {
// It's not possible to allow access to `id` in `createOnce` in ESLint compatibility mode, so we don't
// allow it here either. It's probably not very useful anyway - a rule should know what its own name is!
if (filePath === null) throw new Error('Cannot access `context.id` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.id` in `createOnce`");
return fullRuleName;
},
// Getter for rule options for this rule on this file
get options(): Readonly<Options> {
if (filePath === null) throw new Error('Cannot access `context.options` in `createOnce`');
if (filePath === null) throw new Error("Cannot access `context.options` in `createOnce`");
return ruleDetails.options;
},
/**
+20 -13
View File
@@ -1,8 +1,8 @@
import { typeAssertIs } from '../utils/asserts.js';
import { typeAssertIs } from "../utils/asserts.js";
import type { RuleDetails } from './load.ts';
import type { Range, Ranged } from './location.ts';
import type { Diagnostic } from './report.ts';
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,
@@ -14,7 +14,12 @@ const { prototype: ArrayPrototype, from: ArrayFrom } = Array,
// e.g. `(function*() { yield fix1; yield fix2; })()`
export type FixFn = (
fixer: Fixer,
) => Fix | Array<Fix | null | undefined> | IterableIterator<Fix | null | undefined> | null | undefined;
) =>
| Fix
| Array<Fix | null | undefined>
| IterableIterator<Fix | null | undefined>
| null
| undefined;
// Type of a fix, as returned by `fix` function.
export type Fix = { range: Range; text: string };
@@ -41,10 +46,10 @@ const FIXER = Object.freeze({
return { range: [end, end], text };
},
remove(nodeOrToken: Ranged): Fix {
return { range: nodeOrToken.range, text: '' };
return { range: nodeOrToken.range, text: "" };
},
removeRange(range: Range): Fix {
return { range, text: '' };
return { range, text: "" };
},
replaceText(nodeOrToken: Ranged, text: string): Fix {
return { range: nodeOrToken.range, text };
@@ -86,7 +91,7 @@ export type Fixer = typeof FIXER;
export function getFixes(diagnostic: Diagnostic, ruleDetails: RuleDetails): Fix[] | null {
// ESLint silently ignores non-function `fix` values, so we do the same
const { fix } = diagnostic;
if (typeof fix !== 'function') return null;
if (typeof fix !== "function") return null;
// In ESLint, `fix` is called with `this` as a clone of the `diagnostic` object.
// We just use the original `diagnostic` object - that should be close enough.
@@ -101,7 +106,7 @@ export function getFixes(diagnostic: Diagnostic, ruleDetails: RuleDetails): Fix[
// 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')) {
if (getPrototypeOf(fixes) !== ArrayPrototype || hasOwn(fixes, "toJSON")) {
fixes = ArrayFrom(fixes);
isCloned = true;
}
@@ -139,7 +144,9 @@ export function getFixes(diagnostic: Diagnostic, ruleDetails: RuleDetails): Fix[
// ESLint does not throw this error if `fix` function returns only falsy values.
// We've already exited if that is the case, so we're reproducing that behavior.
if (ruleDetails.isFixable === false) {
throw new Error('Fixable rules must set the `meta.fixable` property to "code" or "whitespace".');
throw new Error(
'Fixable rules must set the `meta.fixable` property to "code" or "whitespace".',
);
}
return fixes;
@@ -163,7 +170,7 @@ function validateAndConformFix(fix: unknown): Fix {
let { range, text } = fix;
// These checks follow ESLint, which throws if `range` is missing or invalid
if (!range || typeof range[0] !== 'number' || typeof range[1] !== 'number') {
if (!range || typeof range[0] !== "number" || typeof range[1] !== "number") {
throw new Error(`Fix has invalid range: ${JSON.stringify(fix, null, 2)}`);
}
@@ -174,9 +181,9 @@ function validateAndConformFix(fix: unknown): Fix {
(fixPrototype === ObjectPrototype || fixPrototype === null) &&
ownKeys(fix).length === 2 &&
getPrototypeOf(range) === ArrayPrototype &&
!hasOwn(range, 'toJSON') &&
!hasOwn(range, "toJSON") &&
range.length === 2 &&
typeof text === 'string'
typeof text === "string"
) {
return fix;
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { lintFile } from './lint.js';
import { loadPlugin } from './load.js';
import { lintFile } from "./lint.js";
import { loadPlugin } from "./load.js";
export { lintFile, loadPlugin };
+1 -1
View File
@@ -23,7 +23,7 @@ export type JsonObject = { [key: string]: JsonValue };
* @param value - The value to deep freeze
*/
export function deepFreezeJsonValue(value: JsonValue): undefined {
if (value === null || typeof value !== 'object') return;
if (value === null || typeof value !== "object") return;
// Circular references are not possible in JSON, so no need to handle them here
if (isArray(value)) {
+18 -13
View File
@@ -1,11 +1,16 @@
import { setupFileContext, resetFileContext } from './context.js';
import { registeredRules } from './load.js';
import { diagnostics } from './report.js';
import { setSettingsForFile, resetSettings } from './settings.js';
import { ast, initAst, resetSourceAndAst, setupSourceForFile } from './source_code.js';
import { typeAssertIs, debugAssertIsNonNull } from '../utils/asserts.js';
import { getErrorMessage } from '../utils/utils.js';
import { addVisitorToCompiled, compiledVisitor, finalizeCompiledVisitor, initCompiledVisitor } from './visitor.js';
import { setupFileContext, resetFileContext } from "./context.js";
import { registeredRules } from "./load.js";
import { diagnostics } from "./report.js";
import { setSettingsForFile, resetSettings } from "./settings.js";
import { ast, initAst, resetSourceAndAst, setupSourceForFile } from "./source_code.js";
import { typeAssertIs, debugAssertIsNonNull } from "../utils/asserts.js";
import { getErrorMessage } from "../utils/utils.js";
import {
addVisitorToCompiled,
compiledVisitor,
finalizeCompiledVisitor,
initCompiledVisitor,
} from "./visitor.js";
// Lazy implementation
/*
@@ -14,9 +19,9 @@ import { walkProgram } from '../generated/walk.js';
*/
// @ts-expect-error we need to generate `.d.ts` file for this module
import { walkProgram } from '../generated/walk.js';
import { walkProgram } from "../generated/walk.js";
import type { AfterHook, BufferWithArrays } from './types.ts';
import type { AfterHook, BufferWithArrays } from "./types.ts";
// Buffers cache.
//
@@ -100,11 +105,11 @@ function lintFileImpl(
typeAssertIs<BufferWithArrays>(buffer);
if (DEBUG) {
if (typeof filePath !== 'string' || filePath.length === 0) {
throw new Error('Expected filePath to be a non-zero length string');
if (typeof filePath !== "string" || filePath.length === 0) {
throw new Error("Expected filePath to be a non-zero length string");
}
if (!Array.isArray(ruleIds) || ruleIds.length === 0) {
throw new Error('Expected `ruleIds` to be a non-zero length array');
throw new Error("Expected `ruleIds` to be a non-zero length array");
}
}
+29 -23
View File
@@ -1,14 +1,14 @@
import { pathToFileURL } from 'node:url';
import { pathToFileURL } from "node:url";
import { createContext } from './context.js';
import { getErrorMessage } from '../utils/utils.js';
import { createContext } from "./context.js";
import { getErrorMessage } from "../utils/utils.js";
import type { Writable } from 'type-fest';
import type { Context } from './context.ts';
import type { JsonValue } from './json.ts';
import type { RuleMeta } from './rule_meta.ts';
import type { AfterHook, BeforeHook, Visitor, VisitorWithHooks } from './types.ts';
import type { SetNullable } from '../utils/types.ts';
import type { Writable } from "type-fest";
import type { Context } from "./context.ts";
import type { JsonValue } from "./json.ts";
import type { RuleMeta } from "./rule_meta.ts";
import type { AfterHook, BeforeHook, Visitor, VisitorWithHooks } from "./types.ts";
import type { SetNullable } from "../utils/types.ts";
const ObjectKeys = Object.keys;
@@ -136,7 +136,7 @@ export async function loadPlugin(path: string, packageName: string | null): Prom
*/
async function loadPluginImpl(path: string, packageName: string | null): Promise<PluginDetails> {
if (DEBUG) {
if (registeredPluginPaths.has(path)) throw new Error('This plugin has already been registered');
if (registeredPluginPaths.has(path)) throw new Error("This plugin has already been registered");
registeredPluginPaths.add(path);
}
@@ -160,19 +160,20 @@ async function loadPluginImpl(path: string, packageName: string | null): Promise
messages: Record<string, string> | null = null;
const ruleMeta = rule.meta;
if (ruleMeta != null) {
if (typeof ruleMeta !== 'object') throw new TypeError('Invalid `rule.meta`');
if (typeof ruleMeta !== "object") throw new TypeError("Invalid `rule.meta`");
const { fixable } = ruleMeta;
if (fixable != null) {
if (fixable !== 'code' && fixable !== 'whitespace') throw new TypeError('Invalid `rule.meta.fixable`');
if (fixable !== "code" && fixable !== "whitespace")
throw new TypeError("Invalid `rule.meta.fixable`");
isFixable = true;
}
// Extract messages for messageId support
const inputMessages = ruleMeta.messages;
if (inputMessages != null) {
if (typeof inputMessages !== 'object') {
throw new TypeError('`rule.meta.messages` must be an object if provided');
if (typeof inputMessages !== "object") {
throw new TypeError("`rule.meta.messages` must be an object if provided");
}
messages = inputMessages;
}
@@ -195,16 +196,19 @@ async function loadPluginImpl(path: string, packageName: string | null): Promise
const context = createContext(`${pluginName}/${ruleName}`, ruleDetails);
(ruleDetails as Writable<RuleDetails>).context = context;
if ('createOnce' in rule) {
if ("createOnce" in rule) {
// TODO: Compile visitor object to array here, instead of repeating compilation on each file
let visitorWithHooks = rule.createOnce(context) as SetNullable<VisitorWithHooks, 'before' | 'after'>;
if (typeof visitorWithHooks !== 'object' || visitorWithHooks === null) {
throw new TypeError('`createOnce` must return an object');
let visitorWithHooks = rule.createOnce(context) as SetNullable<
VisitorWithHooks,
"before" | "after"
>;
if (typeof visitorWithHooks !== "object" || visitorWithHooks === null) {
throw new TypeError("`createOnce` must return an object");
}
let { before: beforeHook, after: afterHook, ...visitor } = visitorWithHooks;
beforeHook = conformHookFn(beforeHook, 'before');
afterHook = conformHookFn(afterHook, 'after');
beforeHook = conformHookFn(beforeHook, "before");
afterHook = conformHookFn(afterHook, "after");
// If empty visitor, make this rule never run by substituting a `before` hook which always returns `false`.
// This means the original `before` hook won't run either.
@@ -247,7 +251,8 @@ function getPluginName(plugin: Plugin, packageName: string | null): string {
if (pluginMeta != null) {
const pluginMetaName = pluginMeta.name;
if (pluginMetaName != null) {
if (typeof pluginMetaName !== 'string') throw new TypeError('`plugin.meta.name` must be a string if defined');
if (typeof pluginMetaName !== "string")
throw new TypeError("`plugin.meta.name` must be a string if defined");
return pluginMetaName;
}
}
@@ -255,7 +260,7 @@ function getPluginName(plugin: Plugin, packageName: string | null): string {
if (packageName !== null) return packageName;
throw new Error(
'Plugin must either define `meta.name`, or be loaded from an NPM package with a `name` field in `package.json`',
"Plugin must either define `meta.name`, or be loaded from an NPM package with a `name` field in `package.json`",
);
}
@@ -268,6 +273,7 @@ function getPluginName(plugin: Plugin, packageName: string | null): string {
*/
function conformHookFn<H>(hookFn: H | null | undefined, hookName: string): H | null {
if (hookFn == null) return null;
if (typeof hookFn !== 'function') throw new TypeError(`\`${hookName}\` hook must be a function if provided`);
if (typeof hookFn !== "function")
throw new TypeError(`\`${hookName}\` hook must be a function if provided`);
return hookFn;
}
+16 -9
View File
@@ -3,10 +3,10 @@
* Functions for converting between `LineColumn` and offsets, and splitting source text into lines.
*/
import { initSourceText, sourceText } from './source_code.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
import { initSourceText, sourceText } from "./source_code.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { Node } from './types.ts';
import type { Node } from "./types.ts";
const { defineProperty } = Object;
@@ -104,8 +104,8 @@ export function resetLines(): void {
* @throws {TypeError|RangeError} If non-numeric `offset`, or `offset` out of range.
*/
export function getLineColumnFromOffset(offset: number): LineColumn {
if (typeof offset !== 'number' || offset < 0 || (offset | 0) !== offset) {
throw new TypeError('Expected `offset` to be a non-negative integer.');
if (typeof offset !== "number" || offset < 0 || (offset | 0) !== offset) {
throw new TypeError("Expected `offset` to be a non-negative integer.");
}
// Build `lines` and `lineStartOffsets` tables if they haven't been already.
@@ -155,9 +155,14 @@ function getLineColumnFromOffsetUnchecked(offset: number): LineColumn {
* or if the `line` is less than or equal to zero, or the line or column is out of the expected range.
*/
export function getOffsetFromLineColumn(loc: LineColumn): number {
if (loc !== null && typeof loc === 'object') {
if (loc !== null && typeof loc === "object") {
const { line, column } = loc;
if (typeof line === 'number' && typeof column === 'number' && (line | 0) === line && (column | 0) === column) {
if (
typeof line === "number" &&
typeof column === "number" &&
(line | 0) === line &&
(column | 0) === column
) {
// Build `lines` and `lineStartOffsets` tables if they haven't been already.
// This also decodes `sourceText` if it wasn't already.
if (lines.length === 0) initLines();
@@ -201,7 +206,9 @@ export function getOffsetFromLineColumn(loc: LineColumn): number {
}
}
throw new TypeError('Expected `loc` to be an object with integer `line` and `column` properties.');
throw new TypeError(
"Expected `loc` to be an object with integer `line` and `column` properties.",
);
}
/**
@@ -226,7 +233,7 @@ export function getNodeLoc(node: Node): Location {
};
// Replace `loc` getter with the calculated value
defineProperty(node, 'loc', { value: loc, writable: true });
defineProperty(node, "loc", { value: loc, writable: true });
return loc;
}
+32 -26
View File
@@ -2,14 +2,14 @@
* `report` function to report errors + diagnostic types.
*/
import { filePath } from './context.js';
import { getFixes } from './fix.js';
import { getOffsetFromLineColumn } from './location.js';
import { filePath } from "./context.js";
import { getFixes } from "./fix.js";
import { getOffsetFromLineColumn } from "./location.js";
import type { RequireAtLeastOne } from 'type-fest';
import type { Fix, FixFn } from './fix.ts';
import type { RuleDetails } from './load.ts';
import type { Location, Ranged } from './location.ts';
import type { RequireAtLeastOne } from "type-fest";
import type { Fix, FixFn } from "./fix.ts";
import type { RuleDetails } from "./load.ts";
import type { Location, Ranged } from "./location.ts";
const { hasOwn, keys: ObjectKeys } = Object;
@@ -22,7 +22,10 @@ const { hasOwn, keys: ObjectKeys } = Object;
*/
// This is the type of the value passed to `Context#report()` by user.
// `DiagnosticReport` (see below) is the type of diagnostics sent to Rust.
export type Diagnostic = RequireAtLeastOne<RequireAtLeastOne<DiagnosticBase, 'node' | 'loc'>, 'message' | 'messageId'>;
export type Diagnostic = RequireAtLeastOne<
RequireAtLeastOne<DiagnosticBase, "node" | "loc">,
"message" | "messageId"
>;
interface DiagnosticBase {
message?: string | null | undefined;
@@ -38,7 +41,7 @@ interface DiagnosticBase {
* Suggested fix.
* NOT IMPLEMENTED YET.
*/
export type Suggestion = RequireAtLeastOne<SuggestionBase, 'desc' | 'messageId'>;
export type Suggestion = RequireAtLeastOne<SuggestionBase, "desc" | "messageId">;
interface SuggestionBase {
desc?: string;
@@ -66,13 +69,13 @@ export const diagnostics: DiagnosticReport[] = [];
* @throws {TypeError} If `diagnostic` is invalid
*/
export function report(diagnostic: Diagnostic, ruleDetails: RuleDetails): void {
if (filePath === null) throw new Error('Cannot report errors in `createOnce`');
if (filePath === null) throw new Error("Cannot report errors in `createOnce`");
// Get message, resolving message from `messageId` if present
let message = getMessage(diagnostic, ruleDetails);
// Interpolate placeholders {{key}} with data values
if (hasOwn(diagnostic, 'data')) {
if (hasOwn(diagnostic, "data")) {
const { data } = diagnostic;
if (data != null) {
message = message.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
@@ -86,37 +89,38 @@ export function report(diagnostic: Diagnostic, ruleDetails: RuleDetails): void {
// TODO: Validate `diagnostic`
let start: number, end: number, loc: Location | undefined;
if (hasOwn(diagnostic, 'loc') && (loc = diagnostic.loc) != null) {
if (hasOwn(diagnostic, "loc") && (loc = diagnostic.loc) != null) {
// `loc`
if (typeof loc !== 'object') throw new TypeError('`loc` must be an object');
if (typeof loc !== "object") throw new TypeError("`loc` must be an object");
start = getOffsetFromLineColumn(loc.start);
end = getOffsetFromLineColumn(loc.end);
} else {
// `node`
const { node } = diagnostic;
if (node == null) throw new TypeError('Either `node` or `loc` is required');
if (typeof node !== 'object') throw new TypeError('`node` must be an object');
if (node == null) throw new TypeError("Either `node` or `loc` is required");
if (typeof node !== "object") throw new TypeError("`node` must be an object");
// ESLint uses `loc` here instead of `range`.
// We can't do that because AST nodes don't have `loc` property yet. In any case, `range` is preferable,
// as otherwise we have to convert `loc` to `range` which is expensive at present.
// TODO: Revisit this once we have `loc` support in AST, and a fast translation table to convert `loc` to `range`.
const { range } = node;
if (range === null || typeof range !== 'object') throw new TypeError('`node.range` must be present');
if (range === null || typeof range !== "object")
throw new TypeError("`node.range` must be present");
start = range[0];
end = range[1];
// Do type validation checks here, to ensure no error in serialization / deserialization.
// Range validation happens on Rust side.
if (
typeof start !== 'number' ||
typeof end !== 'number' ||
typeof start !== "number" ||
typeof end !== "number" ||
start < 0 ||
end < 0 ||
(start | 0) !== start ||
(end | 0) !== end
) {
throw new TypeError('`node.range[0]` and `node.range[1]` must be non-negative integers');
throw new TypeError("`node.range[0]` and `node.range[1]` must be non-negative integers");
}
}
@@ -137,18 +141,18 @@ export function report(diagnostic: Diagnostic, ruleDetails: RuleDetails): void {
* @throws {Error|TypeError} If neither `message` nor `messageId` provided, or of wrong type
*/
function getMessage(diagnostic: Diagnostic, ruleDetails: RuleDetails): string {
if (hasOwn(diagnostic, 'messageId')) {
if (hasOwn(diagnostic, "messageId")) {
const { messageId } = diagnostic as { messageId: string | null | undefined };
if (messageId != null) return resolveMessageFromMessageId(messageId, ruleDetails);
}
if (hasOwn(diagnostic, 'message')) {
if (hasOwn(diagnostic, "message")) {
const { message } = diagnostic;
if (typeof message === 'string') return message;
if (message != null) throw new TypeError('`message` must be a string');
if (typeof message === "string") return message;
if (message != null) throw new TypeError("`message` must be a string");
}
throw new Error('Either `message` or `messageId` is required');
throw new Error("Either `message` or `messageId` is required");
}
/**
@@ -161,14 +165,16 @@ function getMessage(diagnostic: Diagnostic, ruleDetails: RuleDetails): string {
function resolveMessageFromMessageId(messageId: string, ruleDetails: RuleDetails): string {
const { messages } = ruleDetails;
if (messages === null) {
throw new Error(`Cannot use messageId '${messageId}' - rule does not define any messages in \`meta.messages\``);
throw new Error(
`Cannot use messageId '${messageId}' - rule does not define any messages in \`meta.messages\``,
);
}
if (!hasOwn(messages, messageId)) {
throw new Error(
`Unknown messageId '${messageId}'. Available \`messageIds\`: ${ObjectKeys(messages)
.map((msg) => `'${msg}'`)
.join(', ')}`,
.join(", ")}`,
);
}
+2 -2
View File
@@ -14,7 +14,7 @@ export interface RuleMeta {
* of the program that determine how the code looks rather than how it executes.
* These rules work on parts of the code that aren’t specified in the AST.
*/
type?: 'problem' | 'suggestion' | 'layout';
type?: "problem" | "suggestion" | "layout";
/**
* Rule documentation.
*/
@@ -27,7 +27,7 @@ export interface RuleMeta {
* Type of fixes that the rule provides.
* Must be `'code'` or `'whitespace'` if the rule provides fixes.
*/
fixable?: 'code' | 'whitespace';
fixable?: "code" | "whitespace";
/**
* Specifies whether rule can return suggestions.
* Must be `true` if the rule provides suggestions.
+33 -33
View File
@@ -6,12 +6,12 @@ import {
analyze,
type AnalyzeOptions,
type ScopeManager as TSESLintScopeManager,
} from '@typescript-eslint/scope-manager';
import { ast, initAst } from './source_code.js';
import { typeAssertIs, debugAssertIsNonNull } from '../utils/asserts.js';
} from "@typescript-eslint/scope-manager";
import { ast, initAst } from "./source_code.js";
import { typeAssertIs, debugAssertIsNonNull } from "../utils/asserts.js";
import type * as ESTree from '../generated/types.d.ts';
import type { SetNullable } from '../utils/types.ts';
import type * as ESTree from "../generated/types.d.ts";
import type { SetNullable } from "../utils/types.ts";
export interface Scope {
type: ScopeType;
@@ -32,18 +32,18 @@ export interface Scope {
}
export type ScopeType =
| 'block'
| 'catch'
| 'class'
| 'class-field-initializer'
| 'class-static-block'
| 'for'
| 'function'
| 'function-expression-name'
| 'global'
| 'module'
| 'switch'
| 'with';
| "block"
| "catch"
| "class"
| "class-field-initializer"
| "class-static-block"
| "for"
| "function"
| "function-expression-name"
| "global"
| "module"
| "switch"
| "with";
export interface Variable {
name: string;
@@ -74,13 +74,13 @@ export interface Definition {
}
export type DefinitionType =
| 'CatchClause'
| 'ClassName'
| 'FunctionName'
| 'ImplicitGlobalVariable'
| 'ImportBinding'
| 'Parameter'
| 'Variable';
| "CatchClause"
| "ClassName"
| "FunctionName"
| "ImplicitGlobalVariable"
| "ImportBinding"
| "Parameter"
| "Variable";
type Identifier =
| ESTree.IdentifierName
@@ -96,11 +96,11 @@ let tsScopeManager: TSESLintScopeManager | null = null;
// Options for TS-ESLint's `analyze` method.
// `sourceType` property is set before calling `analyze`.
const analyzeOptions: SetNullable<AnalyzeOptions, 'sourceType'> = {
const analyzeOptions: SetNullable<AnalyzeOptions, "sourceType"> = {
globalReturn: false,
jsxFragmentName: null,
jsxPragma: 'React',
lib: ['esnext'],
jsxPragma: "React",
lib: ["esnext"],
sourceType: null,
};
@@ -196,8 +196,8 @@ export type ScopeManager = typeof SCOPE_MANAGER;
*/
export function isGlobalReference(node: ESTree.Node): boolean {
// ref: https://github.com/eslint/eslint/blob/e7cda3bdf1bdd664e6033503a3315ad81736b200/lib/languages/js/source-code/source-code.js#L934-L962
if (!node) throw new TypeError('Missing required argument: `node`');
if (node.type !== 'Identifier') return false;
if (!node) throw new TypeError("Missing required argument: `node`");
if (node.type !== "Identifier") return false;
if (tsScopeManager === null) initTsScopeManager();
debugAssertIsNonNull(tsScopeManager);
@@ -244,19 +244,19 @@ export function getDeclaredVariables(node: ESTree.Node): Variable[] {
*/
export function getScope(node: ESTree.Node): Scope {
// ref: https://github.com/eslint/eslint/blob/e7cda3bdf1bdd664e6033503a3315ad81736b200/lib/languages/js/source-code/source-code.js#L862-L892
if (!node) throw new TypeError('Missing required argument: `node`');
if (!node) throw new TypeError("Missing required argument: `node`");
if (tsScopeManager === null) initTsScopeManager();
debugAssertIsNonNull(tsScopeManager);
const inner = node.type !== 'Program';
const inner = node.type !== "Program";
// Traverse up the AST to find a `Node` whose scope can be acquired.
do {
// @ts-expect-error // TODO: Our types don't quite align yet
const scope = tsScopeManager.acquire(node, inner) as Scope;
if (scope !== null) {
return scope.type === 'function-expression-name' ? scope.childScopes[0] : scope;
return scope.type === "function-expression-name" ? scope.childScopes[0] : scope;
}
// @ts-expect-error - Don't want to create a new variable just to make it nullable
@@ -278,6 +278,6 @@ export function getScope(node: ESTree.Node): Scope {
/* oxlint-disable no-unused-vars */
export function markVariableAsUsed(name: string, refNode: ESTree.Node): boolean {
// TODO: Implement
throw new Error('`context.markVariableAsUsed` not implemented yet');
throw new Error("`context.markVariableAsUsed` not implemented yet");
}
/* oxlint-enable no-unused-vars */
+45 -29
View File
@@ -1,12 +1,12 @@
import esquery from 'esquery';
import visitorKeys from '../generated/keys.js';
import { FUNCTION_NODE_TYPE_IDS, NODE_TYPE_IDS_MAP } from '../generated/type_ids.js';
import esquery from "esquery";
import visitorKeys from "../generated/keys.js";
import { FUNCTION_NODE_TYPE_IDS, NODE_TYPE_IDS_MAP } from "../generated/type_ids.js";
// @ts-expect-error we need to generate `.d.ts` file for this module
import { ancestors } from '../generated/walk.js';
import { ancestors } from "../generated/walk.js";
import type { ESQueryOptions, Selector as EsquerySelector } from 'esquery';
import type { Node as EsqueryNode } from 'estree';
import type { Node, VisitFn } from './types.ts';
import type { ESQueryOptions, Selector as EsquerySelector } from "esquery";
import type { Node as EsqueryNode } from "estree";
import type { Node, VisitFn } from "./types.ts";
const ObjectKeys = Object.keys;
@@ -16,12 +16,12 @@ type NodeTypeId = number;
// Options to call `esquery.matches` with.
const ESQUERY_OPTIONS: ESQueryOptions = {
nodeTypeKey: 'type',
nodeTypeKey: "type",
visitorKeys,
fallback: (node: EsqueryNode) => ObjectKeys(node).filter(filterKey),
matchClass: (_className: unknown, _node: EsqueryNode, _ancestors: EsqueryNode[]) => false, // TODO: Is this right?
};
const filterKey = (key: string) => key !== 'parent' && key !== 'range' && key !== 'loc';
const filterKey = (key: string) => key !== "parent" && key !== "range" && key !== "loc";
// Parsed selector.
interface Selector {
@@ -88,9 +88,12 @@ export function parseSelector(key: string): Selector {
* @param selector - `Selector` which has its `isSimple`, `attributeCount`, and `identifierCount` updated.
* @returns Array of node type IDs the selector matches, or `null` if it matches all nodes.
*/
function analyzeSelector(esquerySelector: EsquerySelector, selector: Selector): NodeTypeId[] | null {
function analyzeSelector(
esquerySelector: EsquerySelector,
selector: Selector,
): NodeTypeId[] | null {
switch (esquerySelector.type) {
case 'identifier': {
case "identifier": {
selector.identifierCount++;
const typeId = NODE_TYPE_IDS_MAP.get(esquerySelector.value);
@@ -100,17 +103,25 @@ function analyzeSelector(esquerySelector: EsquerySelector, selector: Selector):
return typeId === undefined ? EMPTY_TYPE_IDS_ARRAY : [typeId];
}
case 'not':
for (let i = 0, childSelectors = esquerySelector.selectors, len = childSelectors.length; i < len; i++) {
case "not":
for (
let i = 0, childSelectors = esquerySelector.selectors, len = childSelectors.length;
i < len;
i++
) {
analyzeSelector(childSelectors[i], selector);
}
selector.isComplex = true;
return null;
case 'matches': {
case "matches": {
// OR matcher. Matches a node if any of child selectors matches it.
let nodeTypes: NodeTypeId[] | null = [];
for (let i = 0, childSelectors = esquerySelector.selectors, len = childSelectors.length; i < len; i++) {
for (
let i = 0, childSelectors = esquerySelector.selectors, len = childSelectors.length;
i < len;
i++
) {
const childNodeTypes = analyzeSelector(childSelectors[i], selector);
if (childNodeTypes === null) {
nodeTypes = null;
@@ -124,7 +135,7 @@ function analyzeSelector(esquerySelector: EsquerySelector, selector: Selector):
return [...new Set(nodeTypes)];
}
case 'compound': {
case "compound": {
// AND matcher. Only matches a node if all child selectors match it.
const childSelectors = esquerySelector.selectors,
len = childSelectors.length;
@@ -150,31 +161,31 @@ function analyzeSelector(esquerySelector: EsquerySelector, selector: Selector):
return nodeTypes;
}
case 'attribute':
case 'field':
case 'nth-child':
case 'nth-last-child':
case "attribute":
case "field":
case "nth-child":
case "nth-last-child":
selector.isComplex = true;
selector.attributeCount++;
return null;
case 'child':
case 'descendant':
case 'sibling':
case 'adjacent':
case "child":
case "descendant":
case "sibling":
case "adjacent":
selector.isComplex = true;
analyzeSelector(esquerySelector.left, selector);
return analyzeSelector(esquerySelector.right, selector);
case 'class':
case "class":
// TODO: Should TS function types be included in `FUNCTION_NODE_TYPE_IDS`?
// This TODO comment is from ESLint's implementation. Not sure what it means!
// TODO: Abstract into JSLanguage somehow.
if (esquerySelector.name === 'function') return FUNCTION_NODE_TYPE_IDS;
if (esquerySelector.name === "function") return FUNCTION_NODE_TYPE_IDS;
selector.isComplex = true;
return null;
case 'wildcard':
case "wildcard":
return null;
default:
@@ -194,9 +205,14 @@ function analyzeSelector(esquerySelector: EsquerySelector, selector: Selector):
* @param esquerySelector - `EsquerySelector` object
* @returns Wrapped visit function
*/
export function wrapVisitFnWithSelectorMatch(visitFn: VisitFn, esquerySelector: EsquerySelector): VisitFn {
export function wrapVisitFnWithSelectorMatch(
visitFn: VisitFn,
esquerySelector: EsquerySelector,
): VisitFn {
return (node: Node) => {
if (esqueryMatches(node as unknown as EsqueryNode, esquerySelector, ancestors, ESQUERY_OPTIONS)) {
if (
esqueryMatches(node as unknown as EsqueryNode, esquerySelector, ancestors, ESQUERY_OPTIONS)
) {
visitFn(node);
}
};
+3 -3
View File
@@ -2,10 +2,10 @@
* Methods related to settings.
*/
import { deepFreezeJsonValue } from './json.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
import { deepFreezeJsonValue } from "./json.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { JsonObject } from './json.ts';
import type { JsonObject } from "./json.ts";
/**
* Settings for the file being linted.
+16 -16
View File
@@ -1,12 +1,12 @@
import { DATA_POINTER_POS_32, SOURCE_LEN_OFFSET } from '../generated/constants.js';
import { DATA_POINTER_POS_32, SOURCE_LEN_OFFSET } from "../generated/constants.js";
// We use the deserializer which removes `ParenthesizedExpression`s from AST,
// and with `range`, `loc`, and `parent` properties on AST nodes, to match ESLint
// @ts-expect-error we need to generate `.d.ts` file for this module
import { deserializeProgramOnly, resetBuffer } from '../generated/deserialize.js';
import { deserializeProgramOnly, resetBuffer } from "../generated/deserialize.js";
import visitorKeys from '../generated/keys.js';
import * as commentMethods from './comments.js';
import visitorKeys from "../generated/keys.js";
import * as commentMethods from "./comments.js";
import {
getLineColumnFromOffset,
getNodeLoc,
@@ -14,22 +14,22 @@ import {
initLines,
lines,
resetLines,
} from './location.js';
import { resetScopeManager, SCOPE_MANAGER } from './scope.js';
import * as scopeMethods from './scope.js';
import { resetTokens } from './tokens.js';
import * as tokenMethods from './tokens.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
} from "./location.js";
import { resetScopeManager, SCOPE_MANAGER } from "./scope.js";
import * as scopeMethods from "./scope.js";
import { resetTokens } from "./tokens.js";
import * as tokenMethods from "./tokens.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { Program } from '../generated/types.d.ts';
import type { Ranged } from './location.ts';
import type { BufferWithArrays, Node } from './types.ts';
import type { ScopeManager } from './scope.ts';
import type { Program } from "../generated/types.d.ts";
import type { Ranged } from "./location.ts";
import type { BufferWithArrays, Node } from "./types.ts";
import type { ScopeManager } from "./scope.ts";
const { max } = Math;
// Text decoder, for decoding source text from buffer
const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
const textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
// Buffer containing AST. Set before linting a file by `setupSourceForFile`.
let buffer: BufferWithArrays | null = null;
@@ -203,7 +203,7 @@ export const SOURCE_CODE = Object.freeze({
*/
// oxlint-disable-next-line no-unused-vars
getNodeByRangeIndex(index: number): Node | null {
throw new Error('`sourceCode.getNodeByRangeIndex` not implemented yet'); // TODO
throw new Error("`sourceCode.getNodeByRangeIndex` not implemented yet"); // TODO
},
// Location methods
+185 -158
View File
@@ -2,12 +2,12 @@
* `SourceCode` methods related to tokens.
*/
import { createRequire } from 'node:module';
import { sourceText, initSourceText } from './source_code.js';
import { debugAssertIsNonNull } from '../utils/asserts.js';
import { createRequire } from "node:module";
import { sourceText, initSourceText } from "./source_code.js";
import { debugAssertIsNonNull } from "../utils/asserts.js";
import type { Comment, Node, NodeOrToken } from './types.ts';
import type { Span } from './location.ts';
import type { Comment, Node, NodeOrToken } from "./types.ts";
import type { Span } from "./location.ts";
const { max, min } = Math;
@@ -66,59 +66,59 @@ export type Token =
| StringToken
| TemplateToken;
interface BaseToken extends Omit<Span, 'start' | 'end'> {
type: Token['type'];
interface BaseToken extends Omit<Span, "start" | "end"> {
type: Token["type"];
value: string;
}
export interface BooleanToken extends BaseToken {
type: 'Boolean';
type: "Boolean";
}
export type CommentToken = BlockCommentToken | LineCommentToken;
export interface BlockCommentToken extends BaseToken {
type: 'Block';
type: "Block";
}
export interface LineCommentToken extends BaseToken {
type: 'Line';
type: "Line";
}
export interface IdentifierToken extends BaseToken {
type: 'Identifier';
type: "Identifier";
}
export interface JSXIdentifierToken extends BaseToken {
type: 'JSXIdentifier';
type: "JSXIdentifier";
}
export interface JSXTextToken extends BaseToken {
type: 'JSXText';
type: "JSXText";
}
export interface KeywordToken extends BaseToken {
type: 'Keyword';
type: "Keyword";
}
export interface NullToken extends BaseToken {
type: 'Null';
type: "Null";
}
export interface NumericToken extends BaseToken {
type: 'Numeric';
type: "Numeric";
}
export interface PrivateIdentifierToken extends BaseToken {
type: 'PrivateIdentifier';
type: "PrivateIdentifier";
}
export interface PunctuatorToken extends BaseToken {
type: 'Punctuator';
type: "Punctuator";
}
export interface RegularExpressionToken extends BaseToken {
type: 'RegularExpression';
type: "RegularExpression";
regex: {
flags: string;
pattern: string;
@@ -126,11 +126,11 @@ export interface RegularExpressionToken extends BaseToken {
}
export interface StringToken extends BaseToken {
type: 'String';
type: "String";
}
export interface TemplateToken extends BaseToken {
type: 'Template';
type: "Template";
}
// `SkipOptions` object used by `getTokenOrCommentBefore` and `getTokenOrCommentAfter`.
@@ -146,7 +146,7 @@ let tokensWithComments: Token[] | null = null;
// TS-ESLint `parse` method.
// Lazy-loaded only when needed, as it's a lot of code.
// Bundle contains both `@typescript-eslint/typescript-estree` and `typescript`.
let tsEslintParse: typeof import('@typescript-eslint/typescript-estree').parse | null = null;
let tsEslintParse: typeof import("@typescript-eslint/typescript-estree").parse | null = null;
/**
* Initialize TS-ESLint tokens for current file.
@@ -161,11 +161,13 @@ export function initTokens() {
// so is valid both in bundled `dist` output, and in unit tests.
if (tsEslintParse === null) {
const require = createRequire(import.meta.url);
tsEslintParse = (require('./ts_eslint.cjs') as typeof import('@typescript-eslint/typescript-estree')).parse;
tsEslintParse = (
require("./ts_eslint.cjs") as typeof import("@typescript-eslint/typescript-estree")
).parse;
}
({ tokens, comments } = tsEslintParse(sourceText, {
sourceType: 'module',
sourceType: "module",
tokens: true,
comment: true,
// TODO: Set this option dependent on source type
@@ -219,30 +221,32 @@ export function getTokens(
debugAssertIsNonNull(comments);
// Maximum number of tokens to return
const count = typeof countOptions === 'object' && countOptions !== null ? countOptions.count : null;
const count =
typeof countOptions === "object" && countOptions !== null ? countOptions.count : null;
// Number of preceding tokens to additionally return
const beforeCount = typeof countOptions === 'number' ? countOptions : 0;
const beforeCount = typeof countOptions === "number" ? countOptions : 0;
// Number of following tokens to additionally return
afterCount =
(typeof countOptions === 'number' || typeof countOptions === 'undefined') && typeof afterCount === 'number'
(typeof countOptions === "number" || typeof countOptions === "undefined") &&
typeof afterCount === "number"
? afterCount
: 0;
// Function to filter tokens
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
// Whether to return comment tokens
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
// Source array of tokens to search in
@@ -289,7 +293,8 @@ export function getTokens(
// Filter before limiting by `count`
if (filter) nodeTokens = nodeTokens.filter(filter);
if (typeof count === 'number' && count < nodeTokens.length) nodeTokens = nodeTokens.slice(0, count);
if (typeof count === "number" && count < nodeTokens.length)
nodeTokens = nodeTokens.slice(0, count);
return nodeTokens;
}
@@ -302,32 +307,35 @@ export function getTokens(
* If is a function, equivalent to `{ filter: fn }`.
* @returns `Token`, or `null` if all were skipped.
*/
export function getFirstToken(node: Node, skipOptions?: SkipOptions | number | FilterFn | null): Token | null {
export function getFirstToken(
node: Node,
skipOptions?: SkipOptions | number | FilterFn | null,
): Token | null {
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
// Number of tokens to skip
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.skip
: null;
// Filter function
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
// Whether to include comments
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
// Source array of tokens
@@ -356,8 +364,8 @@ export function getFirstToken(node: Node, skipOptions?: SkipOptions | number | F
}
}
if (typeof filter !== 'function') {
if (typeof skip !== 'number') {
if (typeof filter !== "function") {
if (typeof skip !== "number") {
// If no tokens start at or after `rangeStart`, return `null`
if (startIndex >= tokensLength) return null;
// Check if the first candidate token is actually within the range
@@ -371,7 +379,7 @@ export function getFirstToken(node: Node, skipOptions?: SkipOptions | number | F
return firstTokenAfterSkip;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
for (let i = startIndex; i < tokensLength; i++) {
const token = nodeTokens[i];
if (token.range[0] >= rangeEnd) return null; // Token is outside the node
@@ -399,29 +407,32 @@ export function getFirstToken(node: Node, skipOptions?: SkipOptions | number | F
* If is a function, equivalent to `{ filter: fn }`.
* @returns Array of `Token`s.
*/
export function getFirstTokens(node: Node, countOptions?: CountOptions | number | FilterFn | null): Token[] {
export function getFirstTokens(
node: Node,
countOptions?: CountOptions | number | FilterFn | null,
): Token[] {
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
let nodeTokens: Token[] | null = null;
@@ -460,13 +471,13 @@ export function getFirstTokens(node: Node, countOptions?: CountOptions | number
}
}
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd);
return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd));
}
const firstTokens: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) firstTokens.push(token);
@@ -488,31 +499,34 @@ export function getFirstTokens(node: Node, countOptions?: CountOptions | number
* If is a function, equivalent to `{ filter: fn }`.
* @returns `Token`, or `null` if all were skipped.
*/
export function getLastToken(node: Node, skipOptions?: SkipOptions | number | FilterFn | null): Token | null {
export function getLastToken(
node: Node,
skipOptions?: SkipOptions | number | FilterFn | null,
): Token | null {
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
// Number of tokens to skip from the end
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.skip
: null;
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
// Whether to return comment tokens
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
// Source array of tokens to search in
@@ -545,15 +559,15 @@ export function getLastToken(node: Node, skipOptions?: SkipOptions | number | Fi
// TODO: this early return feels iffy
if (lastTokenIndex === nodeTokensLength) return null;
if (typeof filter !== 'function') {
if (typeof skip !== 'number') return nodeTokens[lastTokenIndex] ?? null;
if (typeof filter !== "function") {
if (typeof skip !== "number") return nodeTokens[lastTokenIndex] ?? null;
const token = nodeTokens[lastTokenIndex - skip];
if (token === undefined || token.range[0] < rangeStart) return null;
return token;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
for (let i = lastTokenIndex; i >= 0; i--) {
const token = nodeTokens[i];
if (token.range[0] < rangeStart) return null;
@@ -581,32 +595,35 @@ export function getLastToken(node: Node, skipOptions?: SkipOptions | number | Fi
* If is a function, equivalent to `{ filter: fn }`.
* @returns Array of `Token`s.
*/
export function getLastTokens(node: Node, countOptions?: CountOptions | number | FilterFn | null): Token[] {
export function getLastTokens(
node: Node,
countOptions?: CountOptions | number | FilterFn | null,
): Token[] {
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
// Maximum number of tokens to return
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
// Function to filter tokens
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
// Whether to return comment tokens
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
// Source array of tokens to search in
@@ -646,13 +663,13 @@ export function getLastTokens(node: Node, countOptions?: CountOptions | number |
}
}
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd);
return nodeTokens.slice(max(sliceStart, sliceEnd - count), sliceEnd);
}
const lastTokens: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) lastTokens.push(token);
@@ -685,24 +702,24 @@ export function getTokenBefore(
// Number of tokens preceding the given node to skip
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.skip
: null;
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
// Whether to return comment tokens
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
// Source array of tokens to search in
@@ -732,12 +749,12 @@ export function getTokenBefore(
beforeIndex--;
if (typeof filter !== 'function') {
if (typeof skip !== 'number') return nodeTokens[beforeIndex] ?? null;
if (typeof filter !== "function") {
if (typeof skip !== "number") return nodeTokens[beforeIndex] ?? null;
return nodeTokens[beforeIndex - skip] ?? null;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
while (beforeIndex >= 0) {
const token = nodeTokens[beforeIndex];
if (filter(token)) return token;
@@ -766,7 +783,10 @@ export function getTokenBefore(
* @param skip - Number of tokens to skip.
* @returns `Token`, or `null` if all were skipped.
*/
export function getTokenOrCommentBefore(nodeOrToken: NodeOrToken | Comment, skip?: number): Token | null {
export function getTokenOrCommentBefore(
nodeOrToken: NodeOrToken | Comment,
skip?: number,
): Token | null {
// Equivalent to `return getTokenBefore(nodeOrToken, { includeComments: true, skip });`,
// but reuse a global object to avoid creating a new object on each call
INCLUDE_COMMENTS_SKIP_OPTIONS.skip = skip;
@@ -791,25 +811,25 @@ export function getTokensBefore(
// Maximum number of tokens to return
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? max(0, countOptions)
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
// Function to filter tokens
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
// Whether to return comment tokens
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
// Source array of tokens to search in
@@ -836,13 +856,13 @@ export function getTokensBefore(
}
// Fast path for the common case
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(0, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(0, sliceEnd);
return nodeTokens.slice(sliceEnd - count, sliceEnd);
}
const tokensBefore: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = 0; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) tokensBefore.push(token);
@@ -874,23 +894,23 @@ export function getTokenAfter(
debugAssertIsNonNull(comments);
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? (skipOptions.skip ?? 0)
: 0;
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
// Source array of tokens to search in
@@ -919,12 +939,12 @@ export function getTokenAfter(
}
// Fast path for the common case
if (typeof filter !== 'function') {
if (typeof skip !== 'number') return nodeTokens[startIndex] ?? null;
if (typeof filter !== "function") {
if (typeof skip !== "number") return nodeTokens[startIndex] ?? null;
return nodeTokens[startIndex + skip] ?? null;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
for (let i = startIndex; i < tokensLength; i++) {
const token = nodeTokens[i];
if (filter(token)) return token;
@@ -951,7 +971,10 @@ export function getTokenAfter(
* @param skip - Number of tokens to skip.
* @returns `Token`, or `null` if all were skipped.
*/
export function getTokenOrCommentAfter(nodeOrToken: NodeOrToken | Comment, skip?: number): Token | null {
export function getTokenOrCommentAfter(
nodeOrToken: NodeOrToken | Comment,
skip?: number,
): Token | null {
// Equivalent to `return getTokenAfter(nodeOrToken, { includeComments: true, skip });`,
// but reuse a global object to avoid creating a new object on each call
INCLUDE_COMMENTS_SKIP_OPTIONS.skip = skip;
@@ -975,23 +998,23 @@ export function getTokensAfter(
debugAssertIsNonNull(comments);
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
let nodeTokens: Token[];
@@ -1016,13 +1039,13 @@ export function getTokensAfter(
}
// Fast path for the common case
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart);
return nodeTokens.slice(sliceStart, sliceStart + count);
}
const nodeTokensAfter: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < nodeTokens.length; i++) {
const token = nodeTokens[i];
if (filter(token)) nodeTokensAfter.push(token);
@@ -1059,21 +1082,22 @@ export function getTokensBetween(
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
const count = typeof countOptions === 'object' && countOptions !== null ? countOptions.count : null;
const count =
typeof countOptions === "object" && countOptions !== null ? countOptions.count : null;
const padding = typeof countOptions === 'number' ? countOptions : 0;
const padding = typeof countOptions === "number" ? countOptions : 0;
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
let nodeTokens: Token[];
@@ -1118,13 +1142,13 @@ export function getTokensBetween(
sliceStart = max(0, sliceStart - padding);
sliceEnd += padding;
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd);
return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd));
}
const tokensBetween: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) tokensBetween.push(token);
@@ -1157,23 +1181,23 @@ export function getFirstTokenBetween(
debugAssertIsNonNull(comments);
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.skip
: null;
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
let nodeTokens: Token[] | null = null;
@@ -1204,13 +1228,13 @@ export function getFirstTokenBetween(
}
}
if (typeof filter !== 'function') {
const token = nodeTokens[typeof skip === 'number' ? firstTokenIndex + skip : firstTokenIndex];
if (typeof filter !== "function") {
const token = nodeTokens[typeof skip === "number" ? firstTokenIndex + skip : firstTokenIndex];
if (token === undefined || token.range[0] >= rangeEnd) return null;
return token;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
for (let i = firstTokenIndex; i < tokensLength; i++) {
const token = nodeTokens[i];
if (token.range[0] >= rangeEnd) return null;
@@ -1249,23 +1273,23 @@ export function getFirstTokensBetween(
debugAssertIsNonNull(comments);
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
let nodeTokens: Token[] | null = null;
@@ -1307,13 +1331,13 @@ export function getFirstTokensBetween(
}
}
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd);
return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd));
}
const firstTokens: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) firstTokens.push(token);
@@ -1346,23 +1370,23 @@ export function getLastTokenBetween(
debugAssertIsNonNull(comments);
let skip =
typeof skipOptions === 'number'
typeof skipOptions === "number"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.skip
: null;
const filter =
typeof skipOptions === 'function'
typeof skipOptions === "function"
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
: typeof skipOptions === "object" && skipOptions !== null
? skipOptions.filter
: null;
const includeComments =
typeof skipOptions === 'object' &&
typeof skipOptions === "object" &&
skipOptions !== null &&
'includeComments' in skipOptions &&
"includeComments" in skipOptions &&
skipOptions.includeComments;
let nodeTokens: Token[] | null = null;
@@ -1394,13 +1418,13 @@ export function getLastTokenBetween(
}
// Fast path for the common case
if (typeof filter !== 'function') {
const token = nodeTokens[typeof skip === 'number' ? lastTokenIndex - skip : lastTokenIndex];
if (typeof filter !== "function") {
const token = nodeTokens[typeof skip === "number" ? lastTokenIndex - skip : lastTokenIndex];
if (token === undefined || token.range[0] < rangeStart) return null;
return token;
}
if (typeof skip !== 'number') {
if (typeof skip !== "number") {
for (let i = lastTokenIndex; i >= 0; i--) {
const token = nodeTokens[i];
if (token.range[0] < rangeStart) return null;
@@ -1441,23 +1465,23 @@ export function getLastTokensBetween(
debugAssertIsNonNull(comments);
const count =
typeof countOptions === 'number'
typeof countOptions === "number"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.count
: null;
const filter =
typeof countOptions === 'function'
typeof countOptions === "function"
? countOptions
: typeof countOptions === 'object' && countOptions !== null
: typeof countOptions === "object" && countOptions !== null
? countOptions.filter
: null;
const includeComments =
typeof countOptions === 'object' &&
typeof countOptions === "object" &&
countOptions !== null &&
'includeComments' in countOptions &&
"includeComments" in countOptions &&
countOptions.includeComments;
let nodeTokens: Token[] | null = null;
@@ -1499,13 +1523,13 @@ export function getLastTokensBetween(
}
// Fast path for the common case
if (typeof filter !== 'function') {
if (typeof count !== 'number') return nodeTokens.slice(sliceStart, sliceEnd);
if (typeof filter !== "function") {
if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd);
return nodeTokens.slice(max(sliceStart, sliceEnd - count), sliceEnd);
}
const tokensBetween: Token[] = [];
if (typeof count !== 'number') {
if (typeof count !== "number") {
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) tokensBetween.push(token);
@@ -1526,15 +1550,18 @@ export function getLastTokensBetween(
* @param rangeOptions - Options object.
* @returns The token starting at index, or `null` if no such token.
*/
export function getTokenByRangeStart(index: number, rangeOptions?: RangeOptions | null): Token | null {
export function getTokenByRangeStart(
index: number,
rangeOptions?: RangeOptions | null,
): Token | null {
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);
const includeComments =
typeof rangeOptions === 'object' &&
typeof rangeOptions === "object" &&
rangeOptions !== null &&
'includeComments' in rangeOptions &&
"includeComments" in rangeOptions &&
rangeOptions.includeComments;
let nodeTokens: Token[] | null = null;
+2 -2
View File
@@ -1,3 +1,3 @@
'use strict';
"use strict";
module.exports = require('@typescript-eslint/typescript-estree');
module.exports = require("@typescript-eslint/typescript-estree");
+4 -4
View File
@@ -6,10 +6,10 @@ export interface Visitor {
}
*/
import type { Span } from './location.ts';
import type { Token } from './tokens.ts';
import type { Span } from "./location.ts";
import type { Token } from "./tokens.ts";
import type { VisitorObject as Visitor } from '../generated/visitor.d.ts';
import type { VisitorObject as Visitor } from "../generated/visitor.d.ts";
export type { Visitor };
// Hook function that runs before traversal.
@@ -36,7 +36,7 @@ export type NodeOrToken = Node | Token;
// Comment.
export interface Comment extends Span {
type: 'Line' | 'Block';
type: "Line" | "Block";
value: string;
}
+25 -19
View File
@@ -72,11 +72,15 @@
// for objects created by user code in visitors. If ephemeral user-created objects all fit in new space,
// it will avoid full GC runs, which should greatly improve performance.
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } from '../generated/type_ids.js';
import { parseSelector, wrapVisitFnWithSelectorMatch } from './selector.js';
import {
LEAF_NODE_TYPES_COUNT,
NODE_TYPE_IDS_MAP,
NODE_TYPES_COUNT,
} from "../generated/type_ids.js";
import { parseSelector, wrapVisitFnWithSelectorMatch } from "./selector.js";
import type { CompiledVisitorEntry, EnterExit, Node, VisitFn, Visitor } from './types.ts';
import { typeAssertIs, debugAssertIsNonNull } from '../utils/asserts.js';
import type { CompiledVisitorEntry, EnterExit, Node, VisitFn, Visitor } from "./types.ts";
import { typeAssertIs, debugAssertIsNonNull } from "../utils/asserts.js";
const ObjectKeys = Object.keys,
{ isArray } = Array;
@@ -199,8 +203,8 @@ export function initCompiledVisitor(): void {
* @param visitor - Visitor object
*/
export function addVisitorToCompiled(visitor: Visitor): void {
if (visitor === null || typeof visitor !== 'object') {
throw new TypeError('Visitor returned from `create` method must be an object');
if (visitor === null || typeof visitor !== "object") {
throw new TypeError("Visitor returned from `create` method must be an object");
}
// Exit if is empty visitor
@@ -215,11 +219,11 @@ export function addVisitorToCompiled(visitor: Visitor): void {
let name = keys[i];
let visitFn = visitor[name];
if (typeof visitFn !== 'function') {
if (typeof visitFn !== "function") {
throw new TypeError(`'${name}' property of visitor object is not a function`);
}
const isExit = name.endsWith(':exit');
const isExit = name.endsWith(":exit");
if (isExit) name = name.slice(0, -5);
// TODO: Combine the two hashmaps `NODE_TYPE_IDS_MAP` and selectors cache into one `Map`
@@ -232,13 +236,14 @@ export function addVisitorToCompiled(visitor: Visitor): void {
}
// `*` matches any node without any filtering, so no need to wrap it
if (name !== '*') {
if (name !== "*") {
// Selector.
// Parse selector.
// Wrap `visitFn` so it only executes if the selector matches.
// If selector is simple (unconditionally matches certain types e.g. `:matches(X, Y)`), skip wrapping.
const selector = parseSelector(name);
if (selector.isComplex) visitFn = wrapVisitFnWithSelectorMatch(visitFn, selector.esquerySelector);
if (selector.isComplex)
visitFn = wrapVisitFnWithSelectorMatch(visitFn, selector.esquerySelector);
const { typeIds } = selector;
if (typeIds !== null) {
@@ -448,12 +453,12 @@ type Merger = (...visitFns: VisitFn[]) => VisitFn;
*/
function createMerger(fnCount: number): Merger {
const args = [];
let body = 'return node=>{';
let body = "return node=>{";
for (let i = 1; i <= fnCount; i++) {
args.push(`visit${i}`);
body += `visit${i}(node);`;
}
body += '}';
body += "}";
args.push(body);
// oxlint-disable-next-line typescript-eslint/no-implied-eval
return new Function(...args) as Merger;
@@ -478,11 +483,12 @@ const mergers: (Merger | null)[] = [
visit3(node);
visit4(node);
},
(visit1: VisitFn, visit2: VisitFn, visit3: VisitFn, visit4: VisitFn, visit5: VisitFn) => (node: Node) => {
visit1(node);
visit2(node);
visit3(node);
visit4(node);
visit5(node);
},
(visit1: VisitFn, visit2: VisitFn, visit3: VisitFn, visit4: VisitFn, visit5: VisitFn) =>
(node: Node) => {
visit1(node);
visit2(node);
visit3(node);
visit4(node);
visit5(node);
},
];
+6 -3
View File
@@ -49,11 +49,14 @@ export function debugAssertIsNonNull<T>(value: T | null | undefined): asserts va
* @param message - Message to include in error if condition is `false`,
* or a function which returns the message to include in error if condition is `false` (optional).
*/
export function debugAssert(condition: boolean, message?: string | (() => string)): asserts condition {
export function debugAssert(
condition: boolean,
message?: string | (() => string),
): asserts condition {
if (!DEBUG) return;
if (!condition) {
if (typeof message === 'function') message = message();
throw new Error(message ?? 'Assertion failed');
if (typeof message === "function") message = message();
throw new Error(message ?? "Assertion failed");
}
}
+3 -3
View File
@@ -21,12 +21,12 @@ export function getErrorMessage(err: unknown): string {
if (err instanceof Error) {
// Note: `stack` includes the error message
const { stack } = err;
if (typeof stack === 'string' && stack !== '') return stack;
if (typeof stack === "string" && stack !== "") return stack;
}
const { message } = err as { message?: unknown };
if (typeof message === 'string' && message !== '') return message;
if (typeof message === "string" && message !== "") return message;
} catch {}
return 'Unknown error';
return "Unknown error";
}
+138 -138
View File
@@ -1,23 +1,23 @@
// oxlint-disable jest/no-conditional-expect
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP } from '../src-js/generated/type_ids.js';
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP } from "../src-js/generated/type_ids.js";
import {
addVisitorToCompiled,
compiledVisitor,
finalizeCompiledVisitor,
initCompiledVisitor,
} from '../src-js/plugins/visitor.js';
} from "../src-js/plugins/visitor.js";
import type { EnterExit, Node, VisitFn } from '../src-js/plugins/types.ts';
import type { EnterExit, Node, VisitFn } from "../src-js/plugins/types.ts";
const PROGRAM_TYPE_ID = NODE_TYPE_IDS_MAP.get('Program')!,
EMPTY_STMT_TYPE_ID = NODE_TYPE_IDS_MAP.get('EmptyStatement')!,
IDENTIFIER_TYPE_ID = NODE_TYPE_IDS_MAP.get('Identifier')!,
JSX_IDENTIFIER_TYPE_ID = NODE_TYPE_IDS_MAP.get('JSXIdentifier')!,
FUNC_DECL_TYPE_ID = NODE_TYPE_IDS_MAP.get('FunctionDeclaration')!,
FUNC_EXPR_TYPE_ID = NODE_TYPE_IDS_MAP.get('FunctionExpression')!,
ARROW_FUNC_TYPE_ID = NODE_TYPE_IDS_MAP.get('ArrowFunctionExpression')!;
const PROGRAM_TYPE_ID = NODE_TYPE_IDS_MAP.get("Program")!,
EMPTY_STMT_TYPE_ID = NODE_TYPE_IDS_MAP.get("EmptyStatement")!,
IDENTIFIER_TYPE_ID = NODE_TYPE_IDS_MAP.get("Identifier")!,
JSX_IDENTIFIER_TYPE_ID = NODE_TYPE_IDS_MAP.get("JSXIdentifier")!,
FUNC_DECL_TYPE_ID = NODE_TYPE_IDS_MAP.get("FunctionDeclaration")!,
FUNC_EXPR_TYPE_ID = NODE_TYPE_IDS_MAP.get("FunctionExpression")!,
ARROW_FUNC_TYPE_ID = NODE_TYPE_IDS_MAP.get("ArrowFunctionExpression")!;
const SPAN: Node = {
start: 0,
@@ -29,20 +29,20 @@ const SPAN: Node = {
},
};
describe('compile visitor', () => {
describe("compile visitor", () => {
beforeEach(initCompiledVisitor);
afterEach(finalizeCompiledVisitor);
it('throws if visitor is not an object', () => {
const expectedErr = new TypeError('Visitor returned from `create` method must be an object');
it("throws if visitor is not an object", () => {
const expectedErr = new TypeError("Visitor returned from `create` method must be an object");
expect(() => (addVisitorToCompiled as any)()).toThrow(expectedErr);
expect(() => addVisitorToCompiled(null as any)).toThrow(expectedErr);
expect(() => addVisitorToCompiled(undefined as any)).toThrow(expectedErr);
expect(() => addVisitorToCompiled(true as any)).toThrow(expectedErr);
expect(() => addVisitorToCompiled('xyz' as any)).toThrow(expectedErr);
expect(() => addVisitorToCompiled("xyz" as any)).toThrow(expectedErr);
});
it('throws if visitor property is not a function', () => {
it("throws if visitor property is not a function", () => {
const expectedErr = new TypeError("'Program' property of visitor object is not a function");
expect(() => addVisitorToCompiled({ Program: null as any })).toThrow(expectedErr);
expect(() => addVisitorToCompiled({ Program: undefined as any })).toThrow(expectedErr);
@@ -50,20 +50,20 @@ describe('compile visitor', () => {
expect(() => addVisitorToCompiled({ Program: {} } as any)).toThrow(expectedErr);
});
it('accepts unknown visitor key', () => {
it("accepts unknown visitor key", () => {
expect(() => addVisitorToCompiled({ Foo() {} })).not.toThrow();
expect(() => addVisitorToCompiled({ 'Foo:exit'() {} })).not.toThrow();
expect(() => addVisitorToCompiled({ "Foo:exit"() {} })).not.toThrow();
});
describe('registers enter visitor', () => {
it('for leaf node', () => {
describe("registers enter visitor", () => {
it("for leaf node", () => {
const enter = () => {};
addVisitorToCompiled({ EmptyStatement: enter });
expect(finalizeCompiledVisitor()).toBe(true);
expect(compiledVisitor[EMPTY_STMT_TYPE_ID]).toBe(enter);
});
it('for non-leaf node', () => {
it("for non-leaf node", () => {
const enter = () => {};
addVisitorToCompiled({ Program: enter });
expect(finalizeCompiledVisitor()).toBe(true);
@@ -74,17 +74,17 @@ describe('compile visitor', () => {
});
});
describe('registers exit visitor', () => {
it('for leaf node', () => {
describe("registers exit visitor", () => {
it("for leaf node", () => {
const exit = () => {};
addVisitorToCompiled({ 'EmptyStatement:exit': exit });
addVisitorToCompiled({ "EmptyStatement:exit": exit });
expect(finalizeCompiledVisitor()).toBe(true);
expect(compiledVisitor[EMPTY_STMT_TYPE_ID]).toBe(exit);
});
it('for non-leaf node', () => {
it("for non-leaf node", () => {
const exit = () => {};
addVisitorToCompiled({ 'Program:exit': exit });
addVisitorToCompiled({ "Program:exit": exit });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
@@ -93,28 +93,28 @@ describe('compile visitor', () => {
});
});
describe('registers enter and exit visitors', () => {
describe('for leaf node', () => {
it('defined in order', () => {
describe("registers enter and exit visitors", () => {
describe("for leaf node", () => {
it("defined in order", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter, 'EmptyStatement:exit': exit });
addVisitorToCompiled({ EmptyStatement: enter, "EmptyStatement:exit": exit });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter).toHaveBeenCalledWith(node);
expect(exit).toHaveBeenCalledWith(node);
expect(enter).toHaveBeenCalledBefore(exit);
});
it('defined in reverse order', () => {
it("defined in reverse order", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({ 'EmptyStatement:exit': exit, EmptyStatement: enter });
addVisitorToCompiled({ "EmptyStatement:exit": exit, EmptyStatement: enter });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter).toHaveBeenCalledWith(node);
expect(exit).toHaveBeenCalledWith(node);
@@ -122,11 +122,11 @@ describe('compile visitor', () => {
});
});
describe('for non-leaf node', () => {
it('defined in order', () => {
describe("for non-leaf node", () => {
it("defined in order", () => {
const enter = () => {};
const exit = () => {};
addVisitorToCompiled({ Program: enter, 'Program:exit': exit });
addVisitorToCompiled({ Program: enter, "Program:exit": exit });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
@@ -135,10 +135,10 @@ describe('compile visitor', () => {
expect(enterExit.exit).toBe(exit);
});
it('defined in reverse order', () => {
it("defined in reverse order", () => {
const enter = () => {};
const exit = () => {};
addVisitorToCompiled({ 'Program:exit': exit, Program: enter });
addVisitorToCompiled({ "Program:exit": exit, Program: enter });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
@@ -149,20 +149,20 @@ describe('compile visitor', () => {
});
});
describe('combines multiple visitors', () => {
describe('for leaf node', () => {
it('defined in order', () => {
describe("combines multiple visitors", () => {
describe("for leaf node", () => {
it("defined in order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter1, 'EmptyStatement:exit': exit1 });
addVisitorToCompiled({ EmptyStatement: enter1, "EmptyStatement:exit": exit1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter2, 'EmptyStatement:exit': exit2 });
addVisitorToCompiled({ EmptyStatement: enter2, "EmptyStatement:exit": exit2 });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter1).toHaveBeenCalledWith(node);
expect(exit1).toHaveBeenCalledWith(node);
@@ -173,18 +173,18 @@ describe('compile visitor', () => {
expect(enter2).toHaveBeenCalledBefore(exit2);
});
it('defined in reverse order', () => {
it("defined in reverse order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ 'EmptyStatement:exit': exit1, EmptyStatement: enter1 });
addVisitorToCompiled({ "EmptyStatement:exit": exit1, EmptyStatement: enter1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ 'EmptyStatement:exit': exit2, EmptyStatement: enter2 });
addVisitorToCompiled({ "EmptyStatement:exit": exit2, EmptyStatement: enter2 });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter1).toHaveBeenCalledWith(node);
expect(exit1).toHaveBeenCalledWith(node);
@@ -195,18 +195,18 @@ describe('compile visitor', () => {
expect(enter2).toHaveBeenCalledBefore(exit2);
});
it('defined in mixed order', () => {
it("defined in mixed order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter1, 'EmptyStatement:exit': exit1 });
addVisitorToCompiled({ EmptyStatement: enter1, "EmptyStatement:exit": exit1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ 'EmptyStatement:exit': exit2, EmptyStatement: enter2 });
addVisitorToCompiled({ "EmptyStatement:exit": exit2, EmptyStatement: enter2 });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter1).toHaveBeenCalledWith(node);
expect(exit1).toHaveBeenCalledWith(node);
@@ -217,18 +217,18 @@ describe('compile visitor', () => {
expect(enter2).toHaveBeenCalledBefore(exit2);
});
it('defined in mixed order 2', () => {
it("defined in mixed order 2", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ 'EmptyStatement:exit': exit1, EmptyStatement: enter1 });
addVisitorToCompiled({ "EmptyStatement:exit": exit1, EmptyStatement: enter1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter2, 'EmptyStatement:exit': exit2 });
addVisitorToCompiled({ EmptyStatement: enter2, "EmptyStatement:exit": exit2 });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter1).toHaveBeenCalledWith(node);
expect(exit1).toHaveBeenCalledWith(node);
@@ -240,132 +240,132 @@ describe('compile visitor', () => {
});
});
describe('for non-leaf node', () => {
it('defined in order', () => {
describe("for non-leaf node", () => {
it("defined in order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ Program: enter1, 'Program:exit': exit1 });
addVisitorToCompiled({ Program: enter1, "Program:exit": exit1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ Program: enter2, 'Program:exit': exit2 });
addVisitorToCompiled({ Program: enter2, "Program:exit": exit2 });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
const enterNode = { type: 'Program', ...SPAN };
const enterNode = { type: "Program", ...SPAN };
enterExit.enter!(enterNode);
expect(enter1).toHaveBeenCalledWith(enterNode);
expect(enter2).toHaveBeenCalledWith(enterNode);
const exitNode = { type: 'Program', ...SPAN };
const exitNode = { type: "Program", ...SPAN };
enterExit.exit!(exitNode);
expect(exit1).toHaveBeenCalledWith(exitNode);
expect(exit2).toHaveBeenCalledWith(exitNode);
});
it('defined in reverse order', () => {
it("defined in reverse order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ 'Program:exit': exit1, Program: enter1 });
addVisitorToCompiled({ "Program:exit": exit1, Program: enter1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ 'Program:exit': exit2, Program: enter2 });
addVisitorToCompiled({ "Program:exit": exit2, Program: enter2 });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
const enterNode = { type: 'Program', ...SPAN };
const enterNode = { type: "Program", ...SPAN };
enterExit.enter!(enterNode);
expect(enter1).toHaveBeenCalledWith(enterNode);
expect(enter2).toHaveBeenCalledWith(enterNode);
const exitNode = { type: 'Program', ...SPAN };
const exitNode = { type: "Program", ...SPAN };
enterExit.exit!(exitNode);
expect(exit1).toHaveBeenCalledWith(exitNode);
expect(exit2).toHaveBeenCalledWith(exitNode);
});
it('defined in mixed order', () => {
it("defined in mixed order", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ Program: enter1, 'Program:exit': exit1 });
addVisitorToCompiled({ Program: enter1, "Program:exit": exit1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ 'Program:exit': exit2, Program: enter2 });
addVisitorToCompiled({ "Program:exit": exit2, Program: enter2 });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
const enterNode = { type: 'Program', ...SPAN };
const enterNode = { type: "Program", ...SPAN };
enterExit.enter!(enterNode);
expect(enter1).toHaveBeenCalledWith(enterNode);
expect(enter2).toHaveBeenCalledWith(enterNode);
const exitNode = { type: 'Program', ...SPAN };
const exitNode = { type: "Program", ...SPAN };
enterExit.exit!(exitNode);
expect(exit1).toHaveBeenCalledWith(exitNode);
expect(exit2).toHaveBeenCalledWith(exitNode);
});
it('defined in mixed order 2', () => {
it("defined in mixed order 2", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ 'Program:exit': exit1, Program: enter1 });
addVisitorToCompiled({ "Program:exit": exit1, Program: enter1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ Program: enter2, 'Program:exit': exit2 });
addVisitorToCompiled({ Program: enter2, "Program:exit": exit2 });
expect(finalizeCompiledVisitor()).toBe(true);
const enterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
const enterNode = { type: 'Program', ...SPAN };
const enterNode = { type: "Program", ...SPAN };
enterExit.enter!(enterNode);
expect(enter1).toHaveBeenCalledWith(enterNode);
expect(enter2).toHaveBeenCalledWith(enterNode);
const exitNode = { type: 'Program', ...SPAN };
const exitNode = { type: "Program", ...SPAN };
enterExit.exit!(exitNode);
expect(exit1).toHaveBeenCalledWith(exitNode);
expect(exit2).toHaveBeenCalledWith(exitNode);
});
});
it('many visitors', () => {
it("many visitors", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter1, 'EmptyStatement:exit': exit1 });
addVisitorToCompiled({ EmptyStatement: enter1, "EmptyStatement:exit": exit1 });
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter2, 'EmptyStatement:exit': exit2 });
addVisitorToCompiled({ EmptyStatement: enter2, "EmptyStatement:exit": exit2 });
const enter3 = vi.fn(() => {});
const exit3 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter3, 'EmptyStatement:exit': exit3 });
addVisitorToCompiled({ EmptyStatement: enter3, "EmptyStatement:exit": exit3 });
const enter4 = vi.fn(() => {});
const exit4 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter4, 'EmptyStatement:exit': exit4 });
addVisitorToCompiled({ EmptyStatement: enter4, "EmptyStatement:exit": exit4 });
const enter5 = vi.fn(() => {});
const exit5 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter5, 'EmptyStatement:exit': exit5 });
addVisitorToCompiled({ EmptyStatement: enter5, "EmptyStatement:exit": exit5 });
const enter6 = vi.fn(() => {});
const exit6 = vi.fn(() => {});
addVisitorToCompiled({ EmptyStatement: enter6, 'EmptyStatement:exit': exit6 });
addVisitorToCompiled({ EmptyStatement: enter6, "EmptyStatement:exit": exit6 });
expect(finalizeCompiledVisitor()).toBe(true);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter1).toHaveBeenCalledWith(node);
@@ -394,11 +394,11 @@ describe('compile visitor', () => {
});
});
describe('selectors', () => {
it('`*` adds visitor function for all node types', () => {
describe("selectors", () => {
it("`*` adds visitor function for all node types", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({ '*': enter, '*:exit': exit });
addVisitorToCompiled({ "*": enter, "*:exit": exit });
expect(finalizeCompiledVisitor()).toBe(true);
@@ -416,13 +416,13 @@ describe('compile visitor', () => {
}
});
it('`:matches` adds visitor function for all specified node types', () => {
it("`:matches` adds visitor function for all specified node types", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
// List `EmptyStatement` twice to ensure it's deduped
addVisitorToCompiled({
':matches(Program, EmptyStatement, EmptyStatement)': enter,
':matches(Program, EmptyStatement, EmptyStatement):exit': exit,
":matches(Program, EmptyStatement, EmptyStatement)": enter,
":matches(Program, EmptyStatement, EmptyStatement):exit": exit,
});
expect(finalizeCompiledVisitor()).toBe(true);
@@ -431,7 +431,7 @@ describe('compile visitor', () => {
expect(enterExit.enter).toBe(enter);
expect(enterExit.exit).toBe(exit);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter).toHaveBeenCalledWith(node);
expect(exit).toHaveBeenCalledWith(node);
@@ -442,12 +442,12 @@ describe('compile visitor', () => {
}
});
it('`:matches` with attributes adds visitor function for all specified node types', () => {
it("`:matches` with attributes adds visitor function for all specified node types", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({
':matches(Program[type], EmptyStatement)': enter,
':matches(Program, EmptyStatement[type]):exit': exit,
":matches(Program[type], EmptyStatement)": enter,
":matches(Program, EmptyStatement[type]):exit": exit,
});
expect(finalizeCompiledVisitor()).toBe(true);
@@ -456,15 +456,15 @@ describe('compile visitor', () => {
expect(enterExit.enter).not.toBe(enter);
expect(enterExit.exit).not.toBe(exit);
const enterNode = { type: 'Program', ...SPAN };
const enterNode = { type: "Program", ...SPAN };
enterExit.enter!(enterNode);
expect(enter).toHaveBeenCalledWith(enterNode);
const exitNode = { type: 'Program', ...SPAN };
const exitNode = { type: "Program", ...SPAN };
enterExit.exit!(exitNode);
expect(exit).toHaveBeenCalledWith(exitNode);
const node = { type: 'EmptyStatement', ...SPAN };
const node = { type: "EmptyStatement", ...SPAN };
(compiledVisitor[EMPTY_STMT_TYPE_ID] as VisitFn)(node);
expect(enter).toHaveBeenCalledWith(node);
expect(exit).toHaveBeenCalledWith(node);
@@ -475,29 +475,29 @@ describe('compile visitor', () => {
}
});
it('attributes adds visitor function for all node types, but filtered', () => {
it("attributes adds visitor function for all node types, but filtered", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({
'[name=foo]': enter,
'[name=foo]:exit': exit,
"[name=foo]": enter,
"[name=foo]:exit": exit,
});
expect(finalizeCompiledVisitor()).toBe(true);
for (const typeId of NODE_TYPE_IDS_MAP.values()) {
if (typeId < LEAF_NODE_TYPES_COUNT) {
expect(compiledVisitor[typeId]).toBeTypeOf('function');
expect(compiledVisitor[typeId]).toBeTypeOf("function");
} else {
const enterExit = compiledVisitor[typeId] as EnterExit;
expect(enterExit.enter).toBeTypeOf('function');
expect(enterExit.exit).toBeTypeOf('function');
expect(enterExit.enter).toBeTypeOf("function");
expect(enterExit.exit).toBeTypeOf("function");
expect(enterExit.enter).not.toBe(enter);
expect(enterExit.exit).not.toBe(exit);
}
}
const program = { type: 'Program', ...SPAN };
const program = { type: "Program", ...SPAN };
const programEnterExit = compiledVisitor[PROGRAM_TYPE_ID] as EnterExit;
programEnterExit.enter!(program);
programEnterExit.exit!(program);
@@ -507,43 +507,43 @@ describe('compile visitor', () => {
const identEnterExit = compiledVisitor[IDENTIFIER_TYPE_ID] as EnterExit;
const jsxIdentVisit = compiledVisitor[JSX_IDENTIFIER_TYPE_ID] as VisitFn;
let ident = { type: 'Identifier', name: 'bar', ...SPAN };
let ident = { type: "Identifier", name: "bar", ...SPAN };
identEnterExit.enter!(ident);
identEnterExit.exit!(ident);
expect(enter).not.toHaveBeenCalled();
expect(exit).not.toHaveBeenCalled();
ident = { type: 'JSXIdentifier', name: 'qux', ...SPAN };
ident = { type: "JSXIdentifier", name: "qux", ...SPAN };
jsxIdentVisit(ident);
expect(enter).not.toHaveBeenCalled();
expect(exit).not.toHaveBeenCalled();
ident = { type: 'Identifier', name: 'foo', ...SPAN };
ident = { type: "Identifier", name: "foo", ...SPAN };
identEnterExit.enter!(ident);
expect(enter).toHaveBeenCalledWith(ident);
ident = { type: 'Identifier', name: 'foo', ...SPAN };
ident = { type: "Identifier", name: "foo", ...SPAN };
identEnterExit.exit!(ident);
expect(exit).toHaveBeenCalledWith(ident);
ident = { type: 'JSXIdentifier', name: 'foo', ...SPAN };
ident = { type: "JSXIdentifier", name: "foo", ...SPAN };
jsxIdentVisit(ident);
expect(enter).toHaveBeenCalledWith(ident);
expect(exit).toHaveBeenCalledWith(ident);
});
it('identifier with attribute adds visitor function for only specified node types, and filtered', () => {
it("identifier with attribute adds visitor function for only specified node types, and filtered", () => {
const enter = vi.fn(() => {});
const exit = vi.fn(() => {});
addVisitorToCompiled({
'Identifier[name=foo]': enter,
'Identifier[name=foo]:exit': exit,
"Identifier[name=foo]": enter,
"Identifier[name=foo]:exit": exit,
});
expect(finalizeCompiledVisitor()).toBe(true);
const identEnterExit = compiledVisitor[IDENTIFIER_TYPE_ID] as EnterExit;
expect(identEnterExit.enter).toBeTypeOf('function');
expect(identEnterExit.exit).toBeTypeOf('function');
expect(identEnterExit.enter).toBeTypeOf("function");
expect(identEnterExit.exit).toBeTypeOf("function");
expect(identEnterExit.enter).not.toBe(enter);
expect(identEnterExit.exit).not.toBe(exit);
@@ -552,39 +552,39 @@ describe('compile visitor', () => {
expect(compiledVisitor[typeId]).toBeNull();
}
let ident = { type: 'Identifier', name: 'bar', ...SPAN };
let ident = { type: "Identifier", name: "bar", ...SPAN };
identEnterExit.enter!(ident);
identEnterExit.exit!(ident);
expect(enter).not.toHaveBeenCalled();
expect(exit).not.toHaveBeenCalled();
ident = { type: 'Identifier', name: 'foo', ...SPAN };
ident = { type: "Identifier", name: "foo", ...SPAN };
identEnterExit.enter!(ident);
expect(enter).toHaveBeenCalledWith(ident);
ident = { type: 'Identifier', name: 'foo', ...SPAN };
ident = { type: "Identifier", name: "foo", ...SPAN };
identEnterExit.exit!(ident);
expect(exit).toHaveBeenCalledWith(ident);
});
it('combined', () => {
it("combined", () => {
const enter1 = vi.fn(() => {});
const exit1 = vi.fn(() => {});
addVisitorToCompiled({
'FunctionDeclaration[name=foo]': enter1,
'FunctionDeclaration[name=foo]:exit': exit1,
"FunctionDeclaration[name=foo]": enter1,
"FunctionDeclaration[name=foo]:exit": exit1,
});
const enter2 = vi.fn(() => {});
const exit2 = vi.fn(() => {});
addVisitorToCompiled({
':function': enter2,
':function:exit': exit2,
":function": enter2,
":function:exit": exit2,
});
expect(finalizeCompiledVisitor()).toBe(true);
const funcDeclEnterExit = compiledVisitor[FUNC_DECL_TYPE_ID] as EnterExit;
expect(funcDeclEnterExit.enter).toBeTypeOf('function');
expect(funcDeclEnterExit.exit).toBeTypeOf('function');
expect(funcDeclEnterExit.enter).toBeTypeOf("function");
expect(funcDeclEnterExit.exit).toBeTypeOf("function");
expect(funcDeclEnterExit.enter).not.toBe(enter1);
expect(funcDeclEnterExit.enter).not.toBe(enter2);
expect(funcDeclEnterExit.exit).not.toBe(exit1);
@@ -603,55 +603,55 @@ describe('compile visitor', () => {
expect(compiledVisitor[typeId]).toBeNull();
}
let arrowExpr = { type: 'ArrowFunctionExpression', ...SPAN };
let arrowExpr = { type: "ArrowFunctionExpression", ...SPAN };
arrowFuncEnterExit.enter!(arrowExpr);
expect(enter2).toHaveBeenCalledWith(arrowExpr);
arrowExpr = { type: 'ArrowFunctionExpression', ...SPAN };
arrowExpr = { type: "ArrowFunctionExpression", ...SPAN };
arrowFuncEnterExit.exit!(arrowExpr);
expect(exit2).toHaveBeenCalledWith(arrowExpr);
expect(enter1).not.toHaveBeenCalled();
expect(exit1).not.toHaveBeenCalled();
let funcExpr = { type: 'FunctionExpression', name: 'foo', ...SPAN };
let funcExpr = { type: "FunctionExpression", name: "foo", ...SPAN };
funcExprEnterExit.enter!(funcExpr);
expect(enter2).toHaveBeenCalledWith(funcExpr);
funcExpr = { type: 'FunctionExpression', name: 'foo', ...SPAN };
funcExpr = { type: "FunctionExpression", name: "foo", ...SPAN };
funcExprEnterExit.exit!(funcExpr);
expect(exit2).toHaveBeenCalledWith(funcExpr);
expect(enter1).not.toHaveBeenCalled();
expect(exit1).not.toHaveBeenCalled();
let funcDecl = { type: 'FunctionDeclaration', name: 'bar', ...SPAN };
let funcDecl = { type: "FunctionDeclaration", name: "bar", ...SPAN };
funcDeclEnterExit.enter!(funcDecl);
expect(enter2).toHaveBeenCalledWith(funcDecl);
funcDecl = { type: 'FunctionDeclaration', name: 'bar', ...SPAN };
funcDecl = { type: "FunctionDeclaration", name: "bar", ...SPAN };
funcDeclEnterExit.exit!(funcDecl);
expect(exit2).toHaveBeenCalledWith(funcDecl);
expect(enter1).not.toHaveBeenCalled();
expect(exit1).not.toHaveBeenCalled();
funcDecl = { type: 'FunctionDeclaration', name: 'foo', ...SPAN };
funcDecl = { type: "FunctionDeclaration", name: "foo", ...SPAN };
funcDeclEnterExit.enter!(funcDecl);
expect(enter1).toHaveBeenCalledWith(funcDecl);
expect(enter2).toHaveBeenCalledWith(funcDecl);
funcDecl = { type: 'FunctionDeclaration', name: 'foo', ...SPAN };
funcDecl = { type: "FunctionDeclaration", name: "foo", ...SPAN };
funcDeclEnterExit.exit!(funcDecl);
expect(exit1).toHaveBeenCalledWith(funcDecl);
expect(exit2).toHaveBeenCalledWith(funcDecl);
});
});
describe('`finalizeCompiledVisitor` returns false if all visitors empty', () => {
it('no visitors', () => {
describe("`finalizeCompiledVisitor` returns false if all visitors empty", () => {
it("no visitors", () => {
expect(finalizeCompiledVisitor()).toBe(false);
});
it('1 visitor', () => {
it("1 visitor", () => {
addVisitorToCompiled({});
expect(finalizeCompiledVisitor()).toBe(false);
});
it('multiple visitors', () => {
it("multiple visitors", () => {
addVisitorToCompiled({});
addVisitorToCompiled({});
addVisitorToCompiled({});
+10 -10
View File
@@ -1,10 +1,10 @@
import { join as pathJoin } from 'node:path';
import { describe, it } from 'vitest';
import { PACKAGE_ROOT_PATH, getFixtures, testFixtureWithCommand } from './utils.js';
import { join as pathJoin } from "node:path";
import { describe, it } from "vitest";
import { PACKAGE_ROOT_PATH, getFixtures, testFixtureWithCommand } from "./utils.js";
import type { Fixture } from './utils.ts';
import type { Fixture } from "./utils.ts";
const CLI_PATH = pathJoin(PACKAGE_ROOT_PATH, 'dist/cli.js');
const CLI_PATH = pathJoin(PACKAGE_ROOT_PATH, "dist/cli.js");
// Use current NodeJS executable, rather than `node`, to avoid problems with a Node version manager
// installed on system resulting in using wrong NodeJS version
@@ -22,7 +22,7 @@ const NODE_BIN_PATH = process.execPath;
*
* Fixtures with an `options.json` file containing `"oxlint": false` are skipped.
*/
describe('oxlint CLI', () => {
describe("oxlint CLI", () => {
const fixtures = getFixtures();
for (const fixture of fixtures) {
if (!fixture.options.oxlint) continue;
@@ -40,9 +40,9 @@ async function runFixture(fixture: Fixture): Promise<void> {
// Run Oxlint without `--fix` option
await testFixtureWithCommand({
command: NODE_BIN_PATH,
args: [CLI_PATH, 'files'],
args: [CLI_PATH, "files"],
fixture,
snapshotName: 'output',
snapshotName: "output",
isESLint: false,
});
@@ -50,9 +50,9 @@ async function runFixture(fixture: Fixture): Promise<void> {
if (fixture.options.fix) {
await testFixtureWithCommand({
command: NODE_BIN_PATH,
args: [CLI_PATH, '--fix', 'files'],
args: [CLI_PATH, "--fix", "files"],
fixture,
snapshotName: 'fix',
snapshotName: "fix",
isESLint: false,
});
}
+7 -7
View File
@@ -1,10 +1,10 @@
import { join as pathJoin } from 'node:path';
import { describe, it } from 'vitest';
import { PACKAGE_ROOT_PATH, getFixtures, testFixtureWithCommand } from './utils.js';
import { join as pathJoin } from "node:path";
import { describe, it } from "vitest";
import { PACKAGE_ROOT_PATH, getFixtures, testFixtureWithCommand } from "./utils.js";
import type { Fixture } from './utils.ts';
import type { Fixture } from "./utils.ts";
const ESLINT_PATH = pathJoin(PACKAGE_ROOT_PATH, 'node_modules/.bin/eslint');
const ESLINT_PATH = pathJoin(PACKAGE_ROOT_PATH, "node_modules/.bin/eslint");
/**
* Run ESLint tests for all fixtures in `test/fixtures` which contain an `options.json` file
@@ -14,7 +14,7 @@ const ESLINT_PATH = pathJoin(PACKAGE_ROOT_PATH, 'node_modules/.bin/eslint');
*/
// These tests take longer than 5 seconds on CI, so increase timeout to 10 seconds
// oxlint-disable-next-line jest/valid-describe-callback
describe('ESLint compatibility', { timeout: 10_000 }, () => {
describe("ESLint compatibility", { timeout: 10_000 }, () => {
const fixtures = getFixtures();
for (const fixture of fixtures) {
if (!fixture.options.eslint) continue;
@@ -33,7 +33,7 @@ async function runFixture(fixture: Fixture): Promise<void> {
command: ESLINT_PATH,
args: [],
fixture,
snapshotName: 'eslint',
snapshotName: "eslint",
isESLint: true,
});
}
+4 -4
View File
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,40 +1,40 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
};
},
},
'no-debugger-2': {
"no-debugger-2": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
};
},
},
'no-identifiers-named-foo': {
"no-identifiers-named-foo": {
create(context) {
return {
Identifier(ident) {
if (ident.name == 'foo') {
context.report({ message: 'Unexpected Identifier named foo', node: ident });
if (ident.name == "foo") {
context.report({ message: "Unexpected Identifier named foo", node: ident });
}
},
};
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+16 -13
View File
@@ -1,14 +1,17 @@
import assert from 'node:assert';
import assert from "node:assert";
import type { Comment, Plugin, Rule } from '#oxlint';
import type { Comment, Plugin, Rule } from "#oxlint";
function formatComments(comments: Comment[]): string {
let text = `${comments.length} comment${comments.length === 1 ? '' : 's'}`;
let text = `${comments.length} comment${comments.length === 1 ? "" : "s"}`;
if (comments.length > 0) {
text += '\n';
text += "\n";
text += comments
.map((c, i) => ` [${i}] ${c.type}: ${JSON.stringify(c.value)} at [${c.range[0]}, ${c.range[1]}]`)
.join('\n');
.map(
(c, i) =>
` [${i}] ${c.type}: ${JSON.stringify(c.value)} at [${c.range[0]}, ${c.range[1]}]`,
)
.join("\n");
}
return text;
}
@@ -24,13 +27,13 @@ const testCommentsRule: Rule = {
});
const [, topLevelVariable2, topLevelFunctionExport] = ast.body;
assert(topLevelFunctionExport.type === 'ExportNamedDeclaration');
assert(topLevelFunctionExport.type === "ExportNamedDeclaration");
const topLevelFunction = topLevelFunctionExport.declaration;
assert(topLevelFunction?.type === 'FunctionDeclaration');
assert(topLevelFunction?.type === "FunctionDeclaration");
context.report({
message:
'commentsExistBetween(topLevelVariable2, topLevelFunction): ' +
"commentsExistBetween(topLevelVariable2, topLevelFunction): " +
sourceCode.commentsExistBetween(topLevelVariable2, topLevelFunction),
node: topLevelVariable2,
});
@@ -38,7 +41,7 @@ const testCommentsRule: Rule = {
// Test `commentsExistBetween` returns `false` when start node is after end node
context.report({
message:
'commentsExistBetween(topLevelFunction, topLevelVariable2): ' +
"commentsExistBetween(topLevelFunction, topLevelVariable2): " +
sourceCode.commentsExistBetween(topLevelFunction, topLevelVariable2),
node: topLevelFunction,
});
@@ -48,7 +51,7 @@ const testCommentsRule: Rule = {
const { declarations } = node;
assert(declarations.length >= 1);
const { id, init } = declarations[0];
assert(id.type === 'Identifier');
assert(id.type === "Identifier");
assert(init !== null);
context.report({
@@ -77,10 +80,10 @@ const testCommentsRule: Rule = {
const plugin: Plugin = {
meta: {
name: 'test-comments',
name: "test-comments",
},
rules: {
'test-comments': testCommentsRule,
"test-comments": testCommentsRule,
},
};
+4 -4
View File
@@ -1,4 +1,4 @@
import type { Node, Plugin, Rule } from '#oxlint';
import type { Node, Plugin, Rule } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -14,7 +14,7 @@ const rule: Rule = {
create(context) {
context.report({
message:
'\n' +
"\n" +
`this === rule: ${this === rule}\n` +
`id: ${context.id}\n` +
`filename: ${context.filename}\n` +
@@ -36,10 +36,10 @@ const rule: Rule = {
const plugin: Plugin = {
meta: {
name: 'context-plugin',
name: "context-plugin",
},
rules: {
'log-context': rule,
"log-context": rule,
},
};
+1 -1
View File
@@ -1 +1 @@
console.log('Hello, world!');
console.log("Hello, world!");
+12 -12
View File
@@ -5,61 +5,61 @@
```
x wrapped-context(wrapped-rule): wrapped 1: filename: <fixture>/files/index.js
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule): wrapped 1: id: wrapped-context/wrapped-rule
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule): wrapped 1: source text: "console.log('Hello, world!');\n"
x wrapped-context(wrapped-rule): wrapped 1: source text: "console.log(\"Hello, world!\");\n"
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule2): wrapped 2: filename: <fixture>/files/index.js
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule2): wrapped 2: id: wrapped-context/wrapped-rule2
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule2): wrapped 2: source text: "console.log('Hello, world!');\n"
x wrapped-context(wrapped-rule2): wrapped 2: source text: "console.log(\"Hello, world!\");\n"
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^
`----
x wrapped-context(wrapped-rule): wrapped 1: Identifier: 'console'
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^^^^^^^
`----
x wrapped-context(wrapped-rule2): wrapped 2: Identifier: 'console'
,-[files/index.js:1:1]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^^^^^^^
`----
x wrapped-context(wrapped-rule): wrapped 1: Identifier: 'log'
,-[files/index.js:1:9]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^^^
`----
x wrapped-context(wrapped-rule2): wrapped 2: Identifier: 'log'
,-[files/index.js:1:9]
1 | console.log('Hello, world!');
1 | console.log("Hello, world!");
: ^^^
`----
+16 -9
View File
@@ -1,4 +1,4 @@
import type { Plugin, Rule, Context, Diagnostic, Node } from '#oxlint';
import type { Plugin, Rule, Context, Diagnostic, Node } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -10,16 +10,23 @@ const SPAN: Node = {
},
};
function createWrappedReportFunction(context: Context, prefix: string): (diagnostic: Diagnostic) => void {
function createWrappedReportFunction(
context: Context,
prefix: string,
): (diagnostic: Diagnostic) => void {
const { report } = context;
return (diagnostic: Diagnostic) => report({ ...diagnostic, message: `${prefix}: ${diagnostic.message}` });
return (diagnostic: Diagnostic) =>
report({ ...diagnostic, message: `${prefix}: ${diagnostic.message}` });
}
const baseRule: Rule = {
create(context) {
context.report({ message: `id: ${context.id}`, node: SPAN });
context.report({ message: `filename: ${context.filename}`, node: SPAN });
context.report({ message: `source text: ${JSON.stringify(context.sourceCode.text)}`, node: SPAN });
context.report({
message: `source text: ${JSON.stringify(context.sourceCode.text)}`,
node: SPAN,
});
return {
Identifier(node) {
@@ -31,15 +38,15 @@ const baseRule: Rule = {
const plugin: Plugin = {
meta: {
name: 'wrapped-context',
name: "wrapped-context",
},
rules: {
// Two different forms of context wrapping
'wrapped-rule': {
"wrapped-rule": {
create(context) {
const wrappedContext = Object.create(context, {
report: {
value: createWrappedReportFunction(context, 'wrapped 1'),
value: createWrappedReportFunction(context, "wrapped 1"),
writable: false,
},
});
@@ -47,11 +54,11 @@ const plugin: Plugin = {
return baseRule.create(wrappedContext);
},
},
'wrapped-rule2': {
"wrapped-rule2": {
create(context) {
const wrappedContext = Object.create(Object.getPrototypeOf(context));
Object.assign(wrappedContext, context);
wrappedContext.report = createWrappedReportFunction(context, 'wrapped 2');
wrappedContext.report = createWrappedReportFunction(context, "wrapped 2");
return baseRule.create(wrappedContext);
},
+49 -22
View File
@@ -1,4 +1,4 @@
import type { Node, Plugin, Rule } from '#oxlint';
import type { Node, Plugin, Rule } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -35,17 +35,26 @@ const alwaysRunRule: Rule = {
const getSourceCodeError = tryCatch(() => context.getSourceCode());
const settingsError = tryCatch(() => context.settings);
const parserOptionsError = tryCatch(() => context.parserOptions);
const reportError = tryCatch(() => context.report({ message: 'oh no', node: SPAN }));
const reportError = tryCatch(() => context.report({ message: "oh no", node: SPAN }));
return {
before() {
context.report({ message: `createOnce: call count: ${createOnceCallCount}`, node: SPAN });
context.report({ message: `createOnce: this === rule: ${topLevelThis === alwaysRunRule}`, node: SPAN });
context.report({
message: `createOnce: this === rule: ${topLevelThis === alwaysRunRule}`,
node: SPAN,
});
context.report({ message: `createOnce: cwd: ${cwd}`, node: SPAN });
context.report({ message: `createOnce: getCwd(): ${getCwd}`, node: SPAN });
context.report({ message: `createOnce: id error: ${idError?.message}`, node: SPAN });
context.report({ message: `createOnce: filename error: ${filenameError?.message}`, node: SPAN });
context.report({ message: `createOnce: getFilename() error: ${getFilenameError?.message}`, node: SPAN });
context.report({
message: `createOnce: filename error: ${filenameError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: getFilename() error: ${getFilenameError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: physicalFilename error: ${physicalFilenameError?.message}`,
node: SPAN,
@@ -54,12 +63,30 @@ const alwaysRunRule: Rule = {
message: `createOnce: getPhysicalFilename() error: ${getPhysicalFilenameError?.message}`,
node: SPAN,
});
context.report({ message: `createOnce: options error: ${optionsError?.message}`, node: SPAN });
context.report({ message: `createOnce: sourceCode error: ${sourceCodeError?.message}`, node: SPAN });
context.report({ message: `createOnce: getSourceCode() error: ${getSourceCodeError?.message}`, node: SPAN });
context.report({ message: `createOnce: settings error: ${settingsError?.message}`, node: SPAN });
context.report({ message: `createOnce: parserOptions error: ${parserOptionsError?.message}`, node: SPAN });
context.report({ message: `createOnce: report error: ${reportError?.message}`, node: SPAN });
context.report({
message: `createOnce: options error: ${optionsError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: sourceCode error: ${sourceCodeError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: getSourceCode() error: ${getSourceCodeError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: settings error: ${settingsError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: parserOptions error: ${parserOptionsError?.message}`,
node: SPAN,
});
context.report({
message: `createOnce: report error: ${reportError?.message}`,
node: SPAN,
});
context.report({ message: `before hook: id: ${context.id}`, node: SPAN });
context.report({ message: `before hook: filename: ${context.filename}`, node: SPAN });
@@ -91,7 +118,7 @@ const skipRunRule: Rule = {
context.report({ message: `ident visit fn "${node.name}": should not be output`, node });
},
after() {
context.report({ message: 'after hook: should not be output', node: SPAN });
context.report({ message: "after hook: should not be output", node: SPAN });
},
};
},
@@ -136,10 +163,10 @@ const hooksOnlyRule: Rule = {
return {
// Neither hook should be called, because no AST node visitor functions
before() {
context.report({ message: 'before hook: should not be output', node: SPAN });
context.report({ message: "before hook: should not be output", node: SPAN });
},
after() {
context.report({ message: 'after hook: should not be output', node: SPAN });
context.report({ message: "after hook: should not be output", node: SPAN });
},
};
},
@@ -160,15 +187,15 @@ const noHooksRule: Rule = {
const plugin: Plugin = {
meta: {
name: 'create-once-plugin',
name: "create-once-plugin",
},
rules: {
'always-run': alwaysRunRule,
'skip-run': skipRunRule,
'before-only': beforeOnlyRule,
'after-only': afterOnlyRule,
'only-hooks': hooksOnlyRule,
'no-hooks': noHooksRule,
"always-run": alwaysRunRule,
"skip-run": skipRunRule,
"before-only": beforeOnlyRule,
"after-only": afterOnlyRule,
"only-hooks": hooksOnlyRule,
"no-hooks": noHooksRule,
},
};
@@ -180,5 +207,5 @@ function tryCatch(fn: () => unknown) {
} catch (err) {
return err;
}
throw new Error('Expected function to throw');
throw new Error("Expected function to throw");
}
@@ -1,17 +1,17 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'test-plugin',
name: "test-plugin",
},
rules: {
'no-var': {
"no-var": {
create(context) {
return {
VariableDeclaration(node) {
if (node.kind === 'var') {
if (node.kind === "var") {
context.report({
message: 'Use let or const instead of var',
message: "Use let or const instead of var",
node: node,
});
}
@@ -1 +1 @@
throw new Error('whoops!');
throw new Error("whoops!");
@@ -1,8 +1,8 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
@@ -10,7 +10,7 @@ const plugin: Plugin = {
return {
Program(_program) {},
after() {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
};
},
@@ -1,15 +1,15 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
createOnce(_context) {
return {
before() {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
Program(_program) {},
};
@@ -1,13 +1,13 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
createOnce(_context) {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
},
},
@@ -1,13 +1,13 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
create(_context) {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
},
},
@@ -1,8 +1,8 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
@@ -10,10 +10,10 @@ const plugin: Plugin = {
return {
Identifier(node) {
context.report({
message: 'Identifier found',
message: "Identifier found",
node,
fix() {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
});
},
@@ -1,15 +1,15 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'error-plugin',
name: "error-plugin",
},
rules: {
error: {
create(_context) {
return {
Identifier(_node) {
throw new Error('Whoops!');
throw new Error("Whoops!");
},
};
},
@@ -1,8 +1,8 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {},
};
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'basic-custom-plugin',
name: "basic-custom-plugin",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+10 -10
View File
@@ -1,19 +1,19 @@
import plugin from './plugin.ts';
import plugin from "./plugin.ts";
export default [
{
files: ['files/*.js'],
files: ["files/*.js"],
plugins: {
'define-plugin-plugin': plugin,
"define-plugin-plugin": plugin,
},
rules: {
'define-plugin-plugin/create': 'error',
'define-plugin-plugin/create-once': 'error',
'define-plugin-plugin/create-once-before-false': 'error',
'define-plugin-plugin/create-once-before-only': 'error',
'define-plugin-plugin/create-once-after-only': 'error',
'define-plugin-plugin/create-once-hooks-only': 'error',
'define-plugin-plugin/create-once-no-hooks': 'error',
"define-plugin-plugin/create": "error",
"define-plugin-plugin/create-once": "error",
"define-plugin-plugin/create-once-before-false": "error",
"define-plugin-plugin/create-once-before-only": "error",
"define-plugin-plugin/create-once-after-only": "error",
"define-plugin-plugin/create-once-hooks-only": "error",
"define-plugin-plugin/create-once-no-hooks": "error",
},
},
];
+27 -20
View File
@@ -1,6 +1,6 @@
import { definePlugin } from '#oxlint';
import { definePlugin } from "#oxlint";
import type { Node, Rule } from '#oxlint';
import type { Node, Rule } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -55,7 +55,7 @@ const createOnceRule: Rule = {
context.report({
message:
'before hook:\n' +
"before hook:\n" +
`createOnce call count: ${createOnceCallCount}\n` +
`this === rule: ${topLevelThis === createOnceRule}\n` +
`filename: ${context.filename}`,
@@ -67,13 +67,16 @@ const createOnceRule: Rule = {
visits.push({ fileNum, identNum });
context.report({
message: `ident visit fn "${node.name}":\n` + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message:
`ident visit fn "${node.name}":\n` +
`identNum: ${identNum}\n` +
`filename: ${context.filename}`,
node,
});
},
after() {
context.report({
message: 'after hook:\n' + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message: "after hook:\n" + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
node: SPAN,
});
@@ -89,7 +92,11 @@ const createOnceRule: Rule = {
if (
visits.length !== expectedVisits.length ||
visits.some((v, i) => v.fileNum !== expectedVisits[i].fileNum || v.identNum !== expectedVisits[i].identNum)
visits.some(
(v, i) =>
v.fileNum !== expectedVisits[i].fileNum ||
v.identNum !== expectedVisits[i].identNum,
)
) {
context.report({ message: `Unexpected visits: ${JSON.stringify(visits)}`, node: SPAN });
}
@@ -105,12 +112,12 @@ const createOnceBeforeFalseRule: Rule = {
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
// Only visit AST for `files/2.js`
return context.filename.endsWith('2.js');
return context.filename.endsWith("2.js");
},
Identifier(node) {
context.report({
@@ -120,7 +127,7 @@ const createOnceBeforeFalseRule: Rule = {
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -135,7 +142,7 @@ const createOnceBeforeOnlyRule: Rule = {
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -160,7 +167,7 @@ const createOnceAfterOnlyRule: Rule = {
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -174,13 +181,13 @@ const createOnceHooksOnlyRule: Rule = {
// Neither hook should be called, because no AST node visitor functions
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -203,15 +210,15 @@ const createOnceNoHooksRule: Rule = {
export default definePlugin({
meta: {
name: 'define-plugin-plugin',
name: "define-plugin-plugin",
},
rules: {
create: createRule,
'create-once': createOnceRule,
'create-once-before-false': createOnceBeforeFalseRule,
'create-once-before-only': createOnceBeforeOnlyRule,
'create-once-after-only': createOnceAfterOnlyRule,
'create-once-hooks-only': createOnceHooksOnlyRule,
'create-once-no-hooks': createOnceNoHooksRule,
"create-once": createOnceRule,
"create-once-before-false": createOnceBeforeFalseRule,
"create-once-before-only": createOnceBeforeOnlyRule,
"create-once-after-only": createOnceAfterOnlyRule,
"create-once-hooks-only": createOnceHooksOnlyRule,
"create-once-no-hooks": createOnceNoHooksRule,
},
});
@@ -1,19 +1,19 @@
import plugin from './plugin.ts';
import plugin from "./plugin.ts";
export default [
{
files: ['files/*.js'],
files: ["files/*.js"],
plugins: {
'define-plugin-and-rule-plugin': plugin,
"define-plugin-and-rule-plugin": plugin,
},
rules: {
'define-plugin-and-rule-plugin/create': 'error',
'define-plugin-and-rule-plugin/create-once': 'error',
'define-plugin-and-rule-plugin/create-once-before-false': 'error',
'define-plugin-and-rule-plugin/create-once-before-only': 'error',
'define-plugin-and-rule-plugin/create-once-after-only': 'error',
'define-plugin-and-rule-plugin/create-once-hooks-only': 'error',
'define-plugin-and-rule-plugin/create-once-no-hooks': 'error',
"define-plugin-and-rule-plugin/create": "error",
"define-plugin-and-rule-plugin/create-once": "error",
"define-plugin-and-rule-plugin/create-once-before-false": "error",
"define-plugin-and-rule-plugin/create-once-before-only": "error",
"define-plugin-and-rule-plugin/create-once-after-only": "error",
"define-plugin-and-rule-plugin/create-once-hooks-only": "error",
"define-plugin-and-rule-plugin/create-once-no-hooks": "error",
},
},
];
@@ -1,6 +1,6 @@
import { definePlugin, defineRule } from '#oxlint';
import { definePlugin, defineRule } from "#oxlint";
import type { Node } from '#oxlint';
import type { Node } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -54,7 +54,7 @@ const createOnceRule = defineRule({
context.report({
message:
'before hook:\n' +
"before hook:\n" +
`createOnce call count: ${createOnceCallCount}\n` +
`this === rule: ${topLevelThis === createOnceRule}\n` +
`filename: ${context.filename}`,
@@ -66,13 +66,16 @@ const createOnceRule = defineRule({
visits.push({ fileNum, identNum });
context.report({
message: `ident visit fn "${node.name}":\n` + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message:
`ident visit fn "${node.name}":\n` +
`identNum: ${identNum}\n` +
`filename: ${context.filename}`,
node,
});
},
after() {
context.report({
message: 'after hook:\n' + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message: "after hook:\n" + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
node: SPAN,
});
@@ -88,7 +91,11 @@ const createOnceRule = defineRule({
if (
visits.length !== expectedVisits.length ||
visits.some((v, i) => v.fileNum !== expectedVisits[i].fileNum || v.identNum !== expectedVisits[i].identNum)
visits.some(
(v, i) =>
v.fileNum !== expectedVisits[i].fileNum ||
v.identNum !== expectedVisits[i].identNum,
)
) {
context.report({ message: `Unexpected visits: ${JSON.stringify(visits)}`, node: SPAN });
}
@@ -104,12 +111,12 @@ const createOnceBeforeFalseRule = defineRule({
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
// Only visit AST for `files/2.js`
return context.filename.endsWith('2.js');
return context.filename.endsWith("2.js");
},
Identifier(node) {
context.report({
@@ -119,7 +126,7 @@ const createOnceBeforeFalseRule = defineRule({
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -134,7 +141,7 @@ const createOnceBeforeOnlyRule = defineRule({
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -159,7 +166,7 @@ const createOnceAfterOnlyRule = defineRule({
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -173,13 +180,13 @@ const createOnceHooksOnlyRule = defineRule({
// Neither hook should be called, because no AST node visitor functions
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -202,15 +209,15 @@ const createOnceNoHooksRule = defineRule({
export default definePlugin({
meta: {
name: 'define-plugin-and-rule-plugin',
name: "define-plugin-and-rule-plugin",
},
rules: {
create: createRule,
'create-once': createOnceRule,
'create-once-before-false': createOnceBeforeFalseRule,
'create-once-before-only': createOnceBeforeOnlyRule,
'create-once-after-only': createOnceAfterOnlyRule,
'create-once-hooks-only': createOnceHooksOnlyRule,
'create-once-no-hooks': createOnceNoHooksRule,
"create-once": createOnceRule,
"create-once-before-false": createOnceBeforeFalseRule,
"create-once-before-only": createOnceBeforeOnlyRule,
"create-once-after-only": createOnceAfterOnlyRule,
"create-once-hooks-only": createOnceHooksOnlyRule,
"create-once-no-hooks": createOnceNoHooksRule,
},
});
+10 -10
View File
@@ -1,19 +1,19 @@
import plugin from './plugin.ts';
import plugin from "./plugin.ts";
export default [
{
files: ['files/*.js'],
files: ["files/*.js"],
plugins: {
'define-rule-plugin': plugin,
"define-rule-plugin": plugin,
},
rules: {
'define-rule-plugin/create': 'error',
'define-rule-plugin/create-once': 'error',
'define-rule-plugin/create-once-before-false': 'error',
'define-rule-plugin/create-once-before-only': 'error',
'define-rule-plugin/create-once-after-only': 'error',
'define-rule-plugin/create-once-hooks-only': 'error',
'define-rule-plugin/create-once-no-hooks': 'error',
"define-rule-plugin/create": "error",
"define-rule-plugin/create-once": "error",
"define-rule-plugin/create-once-before-false": "error",
"define-rule-plugin/create-once-before-only": "error",
"define-rule-plugin/create-once-after-only": "error",
"define-rule-plugin/create-once-hooks-only": "error",
"define-rule-plugin/create-once-no-hooks": "error",
},
},
];
+27 -20
View File
@@ -1,6 +1,6 @@
import { defineRule } from '#oxlint';
import { defineRule } from "#oxlint";
import type { Node } from '#oxlint';
import type { Node } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -54,7 +54,7 @@ const createOnceRule = defineRule({
context.report({
message:
'before hook:\n' +
"before hook:\n" +
`createOnce call count: ${createOnceCallCount}\n` +
`this === rule: ${topLevelThis === createOnceRule}\n` +
`filename: ${context.filename}`,
@@ -66,13 +66,16 @@ const createOnceRule = defineRule({
visits.push({ fileNum, identNum });
context.report({
message: `ident visit fn "${node.name}":\n` + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message:
`ident visit fn "${node.name}":\n` +
`identNum: ${identNum}\n` +
`filename: ${context.filename}`,
node,
});
},
after() {
context.report({
message: 'after hook:\n' + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
message: "after hook:\n" + `identNum: ${identNum}\n` + `filename: ${context.filename}`,
node: SPAN,
});
@@ -88,7 +91,11 @@ const createOnceRule = defineRule({
if (
visits.length !== expectedVisits.length ||
visits.some((v, i) => v.fileNum !== expectedVisits[i].fileNum || v.identNum !== expectedVisits[i].identNum)
visits.some(
(v, i) =>
v.fileNum !== expectedVisits[i].fileNum ||
v.identNum !== expectedVisits[i].identNum,
)
) {
context.report({ message: `Unexpected visits: ${JSON.stringify(visits)}`, node: SPAN });
}
@@ -104,12 +111,12 @@ const createOnceBeforeFalseRule = defineRule({
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
// Only visit AST for `files/2.js`
return context.filename.endsWith('2.js');
return context.filename.endsWith("2.js");
},
Identifier(node) {
context.report({
@@ -119,7 +126,7 @@ const createOnceBeforeFalseRule = defineRule({
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -134,7 +141,7 @@ const createOnceBeforeOnlyRule = defineRule({
return {
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -159,7 +166,7 @@ const createOnceAfterOnlyRule = defineRule({
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -173,13 +180,13 @@ const createOnceHooksOnlyRule = defineRule({
// Neither hook should be called, because no AST node visitor functions
before() {
context.report({
message: 'before hook:\n' + `filename: ${context.filename}`,
message: "before hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
after() {
context.report({
message: 'after hook:\n' + `filename: ${context.filename}`,
message: "after hook:\n" + `filename: ${context.filename}`,
node: SPAN,
});
},
@@ -202,15 +209,15 @@ const createOnceNoHooksRule = defineRule({
export default {
meta: {
name: 'define-rule-plugin',
name: "define-rule-plugin",
},
rules: {
create: createRule,
'create-once': createOnceRule,
'create-once-before-false': createOnceBeforeFalseRule,
'create-once-before-only': createOnceBeforeOnlyRule,
'create-once-after-only': createOnceAfterOnlyRule,
'create-once-hooks-only': createOnceHooksOnlyRule,
'create-once-no-hooks': createOnceNoHooksRule,
"create-once": createOnceRule,
"create-once-before-false": createOnceBeforeFalseRule,
"create-once-before-only": createOnceBeforeOnlyRule,
"create-once-after-only": createOnceAfterOnlyRule,
"create-once-hooks-only": createOnceHooksOnlyRule,
"create-once-no-hooks": createOnceNoHooksRule,
},
};
+5 -5
View File
@@ -1,17 +1,17 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'loc-plugin',
name: "loc-plugin",
},
rules: {
'no-bugger': {
"no-bugger": {
create(context) {
let debuggerCount = 0;
return {
Program(_node) {
context.report({
message: 'Bugger debugger debug!',
message: "Bugger debugger debug!",
loc: {
start: { line: 1, column: 2 },
end: { line: 3, column: 5 },
@@ -21,7 +21,7 @@ const plugin: Plugin = {
DebuggerStatement(_node) {
debuggerCount++;
context.report({
message: 'Bugger!',
message: "Bugger!",
loc: {
start: { line: debuggerCount, column: 2 },
end: { line: debuggerCount, column: 8 },
+13 -11
View File
@@ -1,12 +1,12 @@
// oxlint-disable typescript/restrict-template-expressions
import assert from 'node:assert';
import assert from "node:assert";
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'estree-check',
name: "estree-check",
},
rules: {
check: {
@@ -19,7 +19,7 @@ const plugin: Plugin = {
Program(program) {
context.report({
message:
'program:\n' +
"program:\n" +
`start/end: [${program.start},${program.end}]\n` +
`range: [${program.range}]\n` +
`loc: [${JSON.stringify(program.loc)}]`,
@@ -30,7 +30,7 @@ const plugin: Plugin = {
VariableDeclaration(decl) {
visits.push(`${decl.type}: ${decl.kind}`);
},
'VariableDeclaration:exit'(decl) {
"VariableDeclaration:exit"(decl) {
visits.push(`${decl.type}:exit: ${decl.kind}`);
},
VariableDeclarator(decl) {
@@ -71,7 +71,7 @@ const plugin: Plugin = {
// `typeAnnotation` should not be `TSParenthesizedType`
visits.push(`${decl.type}: (typeAnnotation: ${decl.typeAnnotation.type})`);
},
'TSTypeAliasDeclaration:exit'(decl) {
"TSTypeAliasDeclaration:exit"(decl) {
// `typeAnnotation` should not be `TSParenthesizedType`
visits.push(`${decl.type}:exit: (typeAnnotation: ${decl.typeAnnotation.type})`);
},
@@ -84,19 +84,21 @@ const plugin: Plugin = {
},
TSUnionType(union) {
// `types` should not be `TSParenthesizedType`
visits.push(`${union.type}: (types: ${union.types.map((t) => t.type).join(', ')})`);
visits.push(`${union.type}: (types: ${union.types.map((t) => t.type).join(", ")})`);
},
'TSUnionType:exit'(union) {
"TSUnionType:exit"(union) {
// `types` should not be `TSParenthesizedType`
visits.push(`${union.type}:exit: (types: ${union.types.map((t) => t.type).join(', ')})`);
visits.push(
`${union.type}:exit: (types: ${union.types.map((t) => t.type).join(", ")})`,
);
},
TSNumberKeyword(keyword) {
visits.push(keyword.type);
},
'Program:exit'(program) {
"Program:exit"(program) {
visits.push(`${program.type}:exit`);
context.report({
message: `Visited nodes:\n* ${visits.join('\n* ')}`,
message: `Visited nodes:\n* ${visits.join("\n* ")}`,
node: program,
});
},
+34 -33
View File
@@ -1,13 +1,13 @@
import type { Diagnostic, Node, Plugin } from '#oxlint';
import type { Diagnostic, Node, Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'fixes-plugin',
name: "fixes-plugin",
},
rules: {
fixes: {
meta: {
fixable: 'code',
fixable: "code",
},
create(context) {
let debuggerCount = 0;
@@ -17,7 +17,7 @@ const plugin: Plugin = {
let thisIsReport;
const report: Diagnostic = {
message: 'Remove debugger statement',
message: "Remove debugger statement",
node,
fix(fixer) {
thisIsReport = this === report;
@@ -27,73 +27,74 @@ const plugin: Plugin = {
};
context.report(report);
if (!thisIsReport) context.report({ message: `this in fix function is not report object`, node });
if (!thisIsReport)
context.report({ message: `this in fix function is not report object`, node });
},
Identifier(node) {
switch (node.name) {
case 'a':
case "a":
return context.report({
message: 'Replace "a" with "daddy"',
node,
fix(fixer) {
return fixer.replaceText(node, 'daddy');
return fixer.replaceText(node, "daddy");
},
});
case 'b':
case "b":
return context.report({
message: 'Replace "b" with "abacus"',
node,
fix(fixer) {
return fixer.replaceTextRange([node.start, node.end], 'abacus');
return fixer.replaceTextRange([node.start, node.end], "abacus");
},
});
case 'c':
case "c":
return context.report({
message: 'Prefix "c" with "magi"',
node,
fix(fixer) {
return fixer.insertTextBefore(node, 'magi');
return fixer.insertTextBefore(node, "magi");
},
});
case 'd':
case "d":
return context.report({
message: 'Prefix "d" with "damne"',
node,
fix(fixer) {
return fixer.insertTextBeforeRange([node.start, node.end], 'damne');
return fixer.insertTextBeforeRange([node.start, node.end], "damne");
},
});
case 'e':
case "e":
return context.report({
message: 'Postfix "e" with "lephant"',
node,
fix(fixer) {
return fixer.insertTextAfter(node, 'lephant');
return fixer.insertTextAfter(node, "lephant");
},
});
case 'f':
case "f":
return context.report({
message: 'Postfix "f" with "eck"',
node,
fix(fixer) {
return fixer.insertTextAfterRange([node.start, node.end], 'eck');
return fixer.insertTextAfterRange([node.start, node.end], "eck");
},
});
case 'g':
case "g":
return context.report({
message: 'Replace "g" with "numpty"',
node,
fix(fixer) {
// Fixes can be in any order
return [
fixer.insertTextAfter(node, 'ty'),
fixer.insertTextAfter(node, "ty"),
// Test that any object with `range` property works
fixer.replaceText({ range: [node.start, node.end] } as Node, 'mp'),
fixer.insertTextBefore(node, 'nu'),
fixer.replaceText({ range: [node.start, node.end] } as Node, "mp"),
fixer.insertTextBefore(node, "nu"),
];
},
});
case 'h':
case "h":
return context.report({
message: 'Replace "h" with "dangermouse"',
node,
@@ -101,25 +102,25 @@ const plugin: Plugin = {
// Fixes can be in any order
const { range } = node;
return [
fixer.replaceTextRange(range, 'er'),
fixer.insertTextAfterRange(range, 'mouse'),
fixer.insertTextBeforeRange(range, 'dang'),
fixer.replaceTextRange(range, "er"),
fixer.insertTextAfterRange(range, "mouse"),
fixer.insertTextBeforeRange(range, "dang"),
];
},
});
case 'i':
case "i":
return context.report({
message: 'Replace "i" with "granular"',
node,
// `fix` can be a generator function
*fix(fixer) {
yield fixer.insertTextBefore(node, 'gra');
yield fixer.replaceText(node, 'nu');
yield fixer.insertTextBefore(node, "gra");
yield fixer.replaceText(node, "nu");
// Test that any object with `range` property works
yield fixer.insertTextAfter({ range: [node.start, node.end] } as Node, 'lar');
yield fixer.insertTextAfter({ range: [node.start, node.end] } as Node, "lar");
},
});
case 'j':
case "j":
return context.report({
message: 'Replace "j" with "cowabunga"',
node,
@@ -127,9 +128,9 @@ const plugin: Plugin = {
*fix(fixer) {
// Fixes can be in any order
const { range } = node;
yield fixer.insertTextAfterRange(range, 'bunga');
yield fixer.replaceTextRange(range, 'a');
yield fixer.insertTextBeforeRange(range, 'cow');
yield fixer.insertTextAfterRange(range, "bunga");
yield fixer.replaceTextRange(range, "a");
yield fixer.insertTextBeforeRange(range, "cow");
},
});
}
+17 -13
View File
@@ -1,6 +1,6 @@
import assert from 'node:assert';
import assert from "node:assert";
import type { Plugin, Rule, Node } from '#oxlint';
import type { Plugin, Rule, Node } from "#oxlint";
const testRule: Rule = {
create(context) {
@@ -11,7 +11,7 @@ const testRule: Rule = {
context.report({
message:
'\n' +
"\n" +
// Test where 2 nodes are separated, maybe with whitespace in between
`isSpaceBetween(left, right): ${isSpaceBetween(left, right)}\n` +
`isSpaceBetweenTokens(left, right): ${isSpaceBetweenTokens(left, right)}\n` +
@@ -30,11 +30,11 @@ const testRule: Rule = {
});
// Test where 1 node is inside another, not sharing same `start` or `end`
if (right.type === 'BinaryExpression') {
if (right.type === "BinaryExpression") {
const binaryLeft = right.left;
context.report({
message:
'\n' +
"\n" +
`isSpaceBetween(node, binaryLeft): ${isSpaceBetween(node, binaryLeft)}\n` +
`isSpaceBetweenTokens(node, binaryLeft): ${isSpaceBetweenTokens(node, binaryLeft)}\n` +
`isSpaceBetween(binaryLeft, node): ${isSpaceBetween(binaryLeft, node)}\n` +
@@ -45,15 +45,19 @@ const testRule: Rule = {
// Test where 2 nodes are completely adjacent to each other.
// We don't have tokens yet, so adjust ranges of 1 node so they touch.
assert(left.type === 'Identifier');
if (left.name === 'noSpace') {
const leftExtended: Node = { ...left, end: left.end + 1, range: [left.range[0], left.range[1] + 1] };
assert(left.type === "Identifier");
if (left.name === "noSpace") {
const leftExtended: Node = {
...left,
end: left.end + 1,
range: [left.range[0], left.range[1] + 1],
};
assert(leftExtended.end === right.start);
assert(leftExtended.range[1] === right.range[0]);
context.report({
message:
'\n' +
"\n" +
`isSpaceBetween(leftExtended, right): ${isSpaceBetween(leftExtended, right)}\n` +
`isSpaceBetweenTokens(leftExtended, right): ${isSpaceBetweenTokens(leftExtended, right)}\n` +
`isSpaceBetween(right, leftExtended): ${isSpaceBetween(right, leftExtended)}\n` +
@@ -70,7 +74,7 @@ const testRule: Rule = {
// We get this wrong. Should be `false`, but we get `true`.
context.report({
message:
'\n' +
"\n" +
`isSpaceBetween(beforeString, afterString): ${isSpaceBetween(beforeString, afterString)}\n` +
`isSpaceBetweenTokens(beforeString, afterString): ${isSpaceBetweenTokens(beforeString, afterString)}\n` +
`isSpaceBetween(afterString, beforeString): ${isSpaceBetween(afterString, beforeString)}\n` +
@@ -89,7 +93,7 @@ const testRule: Rule = {
// `isSpaceBetweenTokens` is correct for all cases.
context.report({
message:
'\n' +
"\n" +
`isSpaceBetween(openingElement, closingElement): ${isSpaceBetween(openingElement, closingElement)}\n` +
`isSpaceBetweenTokens(openingElement, closingElement): ${isSpaceBetweenTokens(openingElement, closingElement)}\n` +
`isSpaceBetween(closingElement, openingElement): ${isSpaceBetween(closingElement, openingElement)}\n` +
@@ -103,10 +107,10 @@ const testRule: Rule = {
const plugin: Plugin = {
meta: {
name: 'test-plugin',
name: "test-plugin",
},
rules: {
'is-space-between': testRule,
"is-space-between": testRule,
},
};
+4 -4
View File
@@ -1,6 +1,6 @@
import assert from 'node:assert';
import assert from "node:assert";
import type { Plugin, Node } from '#oxlint';
import type { Plugin, Node } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -14,7 +14,7 @@ const SPAN: Node = {
const plugin: Plugin = {
meta: {
name: 'language-options-plugin',
name: "language-options-plugin",
},
rules: {
lang: {
@@ -25,7 +25,7 @@ const plugin: Plugin = {
context.report({
message:
'languageOptions:\n' +
"languageOptions:\n" +
`sourceType: ${languageOptions.sourceType}\n` +
`ecmaVersion: ${languageOptions.ecmaVersion}\n` +
`parserOptions: ${JSON.stringify(languageOptions.parserOptions)}\n` +
+3 -3
View File
@@ -1,14 +1,14 @@
export default {
meta: {
name: 'plugin1',
name: "plugin1",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+4 -4
View File
@@ -1,16 +1,16 @@
'use strict';
"use strict";
module.exports = {
meta: {
name: 'plugin2',
name: "plugin2",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+3 -3
View File
@@ -1,14 +1,14 @@
export default {
meta: {
name: 'plugin3',
name: "plugin3",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+4 -4
View File
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'plugin4',
name: "plugin4",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+5 -5
View File
@@ -1,18 +1,18 @@
'use strict';
"use strict";
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'plugin5',
name: "plugin5",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+4 -4
View File
@@ -1,16 +1,16 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'plugin6',
name: "plugin6",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
@@ -1,14 +1,14 @@
export default {
meta: {
name: 'plugin7',
name: "plugin7",
},
rules: {
'no-debugger': {
"no-debugger": {
create(context) {
return {
DebuggerStatement(debuggerStatement) {
context.report({
message: 'Unexpected Debugger Statement',
message: "Unexpected Debugger Statement",
node: debuggerStatement,
});
},
+5 -5
View File
@@ -1,14 +1,14 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'message-id-error-plugin',
name: "message-id-error-plugin",
},
rules: {
'test-rule': {
"test-rule": {
meta: {
messages: {
validMessage: 'This is a valid message',
validMessage: "This is a valid message",
},
},
create(context) {
@@ -16,7 +16,7 @@ const plugin: Plugin = {
DebuggerStatement(node) {
// Try to use an unknown messageId
context.report({
messageId: 'unknownMessage',
messageId: "unknownMessage",
node,
});
},
+28 -28
View File
@@ -1,84 +1,84 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'interpolation-test',
name: "interpolation-test",
},
rules: {
'no-var': {
"no-var": {
meta: {
messages: {
noData: 'Variables should not use var',
withName: 'Variable `{{name}}` should not use var',
withMultiple: 'Variable `{{name}}` of type `{{type}}` should not use var',
noData: "Variables should not use var",
withName: "Variable `{{name}}` should not use var",
withMultiple: "Variable `{{name}}` of type `{{type}}` should not use var",
// edge cases
missingData: 'Value is `{{value}}` and name is `{{name}}`',
withSpaces: 'Value with spaces is `{{ value }}` and name is `{{ name }}`',
missingData: "Value is `{{value}}` and name is `{{name}}`",
withSpaces: "Value with spaces is `{{ value }}` and name is `{{ name }}`",
},
},
create(context) {
return {
VariableDeclaration(node) {
if (node.kind === 'var') {
if (node.kind === "var") {
const declarations = node.declarations;
if (declarations.length > 0) {
const firstDeclaration = declarations[0];
if (firstDeclaration.id.type === 'Identifier') {
if (firstDeclaration.id.type === "Identifier") {
const name = firstDeclaration.id.name;
if (name === 'testWithNoData') {
if (name === "testWithNoData") {
// Test with no placeholders, no data
context.report({
messageId: 'noData',
messageId: "noData",
node,
});
} else if (name === 'testWithName') {
} else if (name === "testWithName") {
// Test with single placeholder
context.report({
messageId: 'withName',
messageId: "withName",
node,
data: { name },
});
} else if (name === 'testWithNameNoData') {
} else if (name === "testWithNameNoData") {
// Test with single placeholder, but no data
context.report({
messageId: 'withName',
messageId: "withName",
node,
});
} else if (name === 'testWithMultiple') {
} else if (name === "testWithMultiple") {
// Test with multiple placeholders
context.report({
messageId: 'withMultiple',
messageId: "withMultiple",
node,
data: {
name,
type: 'string',
type: "string",
},
});
} else if (name === 'testWithMultipleNoData') {
} else if (name === "testWithMultipleNoData") {
// Test with multiple placeholders, but no data
context.report({
messageId: 'withMultiple',
messageId: "withMultiple",
node,
});
} else if (name === 'testWithMissingData') {
} else if (name === "testWithMissingData") {
// Test missing data - placeholder should remain
context.report({
messageId: 'missingData',
messageId: "missingData",
node,
data: {
value: 'example',
value: "example",
// name is missing
},
});
} else if (name === 'testWithSpaces') {
} else if (name === "testWithSpaces") {
// Test whitespace in placeholders
context.report({
messageId: 'withSpaces',
messageId: "withSpaces",
node,
data: {
value: 'hello',
name: 'world',
value: "hello",
name: "world",
},
});
}
+9 -9
View File
@@ -1,32 +1,32 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const MESSAGE_ID_ERROR = 'no-var/error';
const MESSAGE_ID_ERROR = "no-var/error";
const messages = {
[MESSAGE_ID_ERROR]: 'Unexpected var, use let or const instead.',
[MESSAGE_ID_ERROR]: "Unexpected var, use let or const instead.",
};
const plugin: Plugin = {
meta: {
name: 'message-id-plugin',
name: "message-id-plugin",
},
rules: {
'no-var': {
"no-var": {
meta: {
messages,
},
create(context) {
return {
VariableDeclaration(node) {
if (node.kind === 'var') {
if (node.kind === "var") {
const decl = node.declarations[0];
const varName = decl.id.type === 'Identifier' ? decl.id.name : '<destructured>';
const varName = decl.id.type === "Identifier" ? decl.id.name : "<destructured>";
if (varName === 'reportUsingNode') {
if (varName === "reportUsingNode") {
context.report({
messageId: MESSAGE_ID_ERROR,
node,
});
} else if (varName === 'reportUsingRange') {
} else if (varName === "reportUsingRange") {
context.report({
messageId: MESSAGE_ID_ERROR,
loc: node.loc,
+23 -23
View File
@@ -1,74 +1,74 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'interpolation-test',
name: "interpolation-test",
},
rules: {
'no-var': {
"no-var": {
create(context) {
return {
VariableDeclaration(node) {
if (node.kind === 'var') {
if (node.kind === "var") {
const declarations = node.declarations;
if (declarations.length > 0) {
const firstDeclaration = declarations[0];
if (firstDeclaration.id.type === 'Identifier') {
if (firstDeclaration.id.type === "Identifier") {
const name = firstDeclaration.id.name;
if (name === 'testWithNoData') {
if (name === "testWithNoData") {
// Test with no placeholders, no data
context.report({
message: 'Variables should not use var',
message: "Variables should not use var",
node,
});
} else if (name === 'testWithName') {
} else if (name === "testWithName") {
// Test with single placeholder
context.report({
message: 'Variable `{{name}}` should not use var',
message: "Variable `{{name}}` should not use var",
node,
data: { name },
});
} else if (name === 'testWithNameNoData') {
} else if (name === "testWithNameNoData") {
// Test with single placeholder, but no data
context.report({
message: 'Variable `{{name}}` should not use var',
message: "Variable `{{name}}` should not use var",
node,
});
} else if (name === 'testWithMultiple') {
} else if (name === "testWithMultiple") {
// Test with multiple placeholders
context.report({
message: 'Variable `{{name}}` of type `{{type}}` should not use var',
message: "Variable `{{name}}` of type `{{type}}` should not use var",
node,
data: {
name,
type: 'string',
type: "string",
},
});
} else if (name === 'testWithMultipleNoData') {
} else if (name === "testWithMultipleNoData") {
// Test with multiple placeholders, but no data
context.report({
message: 'Variable `{{name}}` of type `{{type}}` should not use var',
message: "Variable `{{name}}` of type `{{type}}` should not use var",
node,
});
} else if (name === 'testWithMissingData') {
} else if (name === "testWithMissingData") {
// Test missing data - placeholder should remain
context.report({
message: 'Value is `{{value}}` and name is `{{name}}`',
message: "Value is `{{value}}` and name is `{{name}}`",
node,
data: {
value: 'example',
value: "example",
// name is missing
},
});
} else if (name === 'testWithSpaces') {
} else if (name === "testWithSpaces") {
// Test whitespace in placeholders
context.report({
message: 'Value with spaces is `{{ value }}` and name is `{{ name }}`',
message: "Value with spaces is `{{ value }}` and name is `{{ name }}`",
node,
data: {
value: 'hello',
name: 'world',
value: "hello",
name: "world",
},
});
}
+3 -3
View File
@@ -1,8 +1,8 @@
import type { Plugin } from '#oxlint';
import type { Plugin } from "#oxlint";
const plugin: Plugin = {
meta: {
name: 'parents',
name: "parents",
},
rules: {
check: {
@@ -16,7 +16,7 @@ const plugin: Plugin = {
.getAncestors(node)
// @ts-expect-error - Shouldn't be an error. We need to fix our types.
.map((node) => node.type)
.join(', ')} ]`,
.join(", ")} ]`,
node,
});
}
+1 -1
View File
@@ -1,2 +1,2 @@
// Simple file to test parserServices access
console.log('testing parserServices');
console.log("testing parserServices");
+1 -1
View File
@@ -7,7 +7,7 @@
,-[files/index.js:1:1]
1 | // Simple file to test parserServices access
: ^
2 | console.log('testing parserServices');
2 | console.log("testing parserServices");
`----
Found 0 warnings and 1 error.
+3 -3
View File
@@ -1,4 +1,4 @@
import type { Plugin, Node } from '#oxlint';
import type { Plugin, Node } from "#oxlint";
const SPAN: Node = {
start: 0,
@@ -12,10 +12,10 @@ const SPAN: Node = {
const plugin: Plugin = {
meta: {
name: 'parser-services-plugin',
name: "parser-services-plugin",
},
rules: {
'check-parser-services': {
"check-parser-services": {
create(context) {
context.report({
message: `typeof context.sourceCode.parserServices: ${typeof context.sourceCode.parserServices}`,
Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff. Show more