mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
1 parent
41ddbf5b55
commit
523a30a016
209 files changed
+8139
-7252
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
@@ -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()}`);
|
||||
}
|
||||
@@ -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,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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './bindings.js';
|
||||
export { formatEmbeddedCode } from './embedded.js';
|
||||
export * from "./bindings.js";
|
||||
export { formatEmbeddedCode } from "./embedded.js";
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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 ---------
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import { configDefaults, defineConfig } from 'vitest/config';
|
||||
import { configDefaults, defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
|
||||
@@ -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!");
|
||||
@@ -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
@@ -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) => {
|
||||
|
||||
@@ -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,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;
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
module.exports = require('@typescript-eslint/typescript-estree');
|
||||
module.exports = require("@typescript-eslint/typescript-estree");
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
];
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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({});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 @@
|
||||
console.log('Hello, world!');
|
||||
console.log("Hello, world!");
|
||||
+12
-12
@@ -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
@@ -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
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
+4
-4
@@ -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
@@ -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
@@ -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",
|
||||
},
|
||||
},
|
||||
];
|
||||
+27
-20
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,2 +1,2 @@
|
||||
// Simple file to test parserServices access
|
||||
console.log('testing parserServices');
|
||||
console.log("testing parserServices");
|
||||
@@ -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
@@ -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
Reference in new issue
Block a user