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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 89 additions & 90 deletions Cargo.lock

Large diffs are not rendered by default.

27 changes: 12 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ ignored = [
"css-module-lexer",
"html5gum",
"prettyplease",
"proc-macro2",
"quote",
"string_cache",
"syn",
]

[workspace.package]
Expand Down Expand Up @@ -312,7 +309,7 @@ xxhash-rust = "0.8.15"
zip = { version = "7.2", default-features = false, features = ["deflate-flate2-zlib-rs"] }

# oxc crates with the same version
oxc = { version = "0.142.0", features = [
oxc = { version = "0.143.0", features = [
"ast_visit",
"transformer",
"minifier",
Expand All @@ -324,17 +321,17 @@ oxc = { version = "0.142.0", features = [
"regular_expression",
"cfg",
] }
oxc_allocator = { version = "0.142.0", features = ["pool"] }
oxc_ast = "0.142.0"
oxc_ecmascript = "0.142.0"
oxc_parser = "0.142.0"
oxc_span = "0.142.0"
oxc_napi = "0.142.0"
oxc_str = "0.142.0"
oxc_minify_napi = "0.142.0"
oxc_parser_napi = "0.142.0"
oxc_transform_napi = "0.142.0"
oxc_traverse = "0.142.0"
oxc_allocator = { version = "0.143.0", features = ["pool"] }
oxc_ast = "0.143.0"
oxc_ecmascript = "0.143.0"
oxc_parser = "0.143.0"
oxc_span = "0.143.0"
oxc_napi = "0.143.0"
oxc_str = "0.143.0"
oxc_minify_napi = "0.143.0"
oxc_parser_napi = "0.143.0"
oxc_transform_napi = "0.143.0"
oxc_traverse = "0.143.0"

# oxc crates in their own repos
oxc_index = { version = "5", features = ["rayon", "serde"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Allowing / Denying Multiple Lints
* `all` - All categories listed above except `nursery`. Does not enable plugins
automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Allowing / Denying Multiple Lints:
* `nursery` - New lints that are still under development
* `all` - All categories listed above except `nursery`. Does not enable plugins automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Allowing / Denying Multiple Lints:
* `nursery` - New lints that are still under development
* `all` - All categories listed above except `nursery`. Does not enable plugins automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins:
Expand Down Expand Up @@ -122,7 +122,7 @@ Allowing / Denying Multiple Lints:
* `nursery` - New lints that are still under development
* `all` - All categories listed above except `nursery`. Does not enable plugins automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins:
Expand Down Expand Up @@ -215,7 +215,7 @@ Allowing / Denying Multiple Lints:
* `nursery` - New lints that are still under development
* `all` - All categories listed above except `nursery`. Does not enable plugins automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Allowing / Denying Multiple Lints:
* `nursery` - New lints that are still under development
* `all` - All categories listed above except `nursery`. Does not enable plugins automatically.
-A, --allow=NAME Allow the rule or category (suppress the lint)
-W, --warn=NAME Deny the rule or category (emit a warning)
-W, --warn=NAME Warn on the rule or category (emit a warning)
-D, --deny=NAME Deny the rule or category (emit an error)

Enable/Disable Plugins:
Expand Down
77 changes: 50 additions & 27 deletions crates/vp_static_config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,20 @@ fn extract_config_from_expr(
match first_arg_expr.get_inner_expression() {
Expression::ObjectExpression(obj) => extract_object_fields(obj),
Expression::ArrowFunctionExpression(arrow) => {
extract_config_from_function_body(&arrow.body)
// As of oxc 0.143 an arrow body is an `ArrowFunctionBody`
// enum: either a block body (`() => { ... }`) or a concise
// expression body (`() => ({ ... })`).
if let Some(body) = arrow.body.as_function_body() {
extract_config_from_function_body(body)
} else if let Some(expr) = arrow.body.as_expression() {
if let Expression::ObjectExpression(obj) = expr.get_inner_expression() {
extract_object_fields(obj)
} else {
FieldMap::unanalyzable()
}
} else {
FieldMap::unanalyzable()
}
}
Expression::FunctionExpression(func) => {
let Some(body) = func.body.as_ref() else {
Expand All @@ -242,11 +255,12 @@ fn extract_config_from_expr(
}
}

/// Extract the config object from the body of a function passed to `defineConfig`.
/// Extract the config object from the block body of a function passed to `defineConfig`.
///
/// Handles two patterns:
/// - Concise arrow body: `() => ({ ... })` — body has a single `ExpressionStatement`
/// - Block body with exactly one return: `() => { ... return { ... }; }`
/// Handles a block body with exactly one return: `() => { ... return { ... }; }`
/// or `function() { return { ... }; }`. Concise arrow bodies (`() => ({ ... })`)
/// are handled directly by the caller, since oxc represents them as an expression
/// rather than a `FunctionBody`.
///
/// Returns `FieldMap::unanalyzable()` if the body contains multiple `return` statements
/// (at any nesting depth), since the returned config would depend on runtime control flow.
Expand All @@ -257,25 +271,14 @@ fn extract_config_from_function_body(body: &oxc_ast::ast::FunctionBody<'_>) -> F
}

for stmt in &body.statements {
match stmt {
Statement::ReturnStatement(ret) => {
let Some(arg) = ret.argument.as_ref() else {
return FieldMap::unanalyzable();
};
if let Expression::ObjectExpression(obj) = arg.get_inner_expression() {
return extract_object_fields(obj);
}
if let Statement::ReturnStatement(ret) = stmt {
let Some(arg) = ret.argument.as_ref() else {
return FieldMap::unanalyzable();
};
if let Expression::ObjectExpression(obj) = arg.get_inner_expression() {
return extract_object_fields(obj);
}
Statement::ExpressionStatement(expr_stmt) => {
// Concise arrow: `() => ({ ... })` is represented as ExpressionStatement
if let Expression::ObjectExpression(obj) =
expr_stmt.expression.get_inner_expression()
{
return extract_object_fields(obj);
}
}
_ => {}
return FieldMap::unanalyzable();
}
}
FieldMap::unanalyzable()
Expand Down Expand Up @@ -1256,13 +1259,30 @@ mod tests {
fn define_config_arrow_no_return_object() {
// Arrow function that doesn't return an object literal
assert_non_static(
&parse_js_ts_config(
&parse(
r"
import { defineConfig } from 'vite-plus';

export default defineConfig(({ mode }) => {
return someFunction();
});
",
"ts",
),
"run",
);
}

#[test]
fn define_config_arrow_expression_body_no_object() {
// Concise arrow body that is not an object literal. oxc represents it as an
// expression rather than a function body, so it takes its own extraction path.
assert_non_static(
&parse(
r"
import { defineConfig } from 'vite-plus';

export default defineConfig(() => someFunction());
",
),
"run",
);
Expand All @@ -1272,16 +1292,17 @@ mod tests {
fn define_config_arrow_multiple_returns() {
// Multiple top-level returns → not analyzable
assert_non_static(
&parse_js_ts_config(
&parse(
r"
import { defineConfig } from 'vite-plus';

export default defineConfig(({ mode }) => {
if (mode === 'production') {
return { run: { cacheScripts: true } };
}
return { run: { cacheScripts: false } };
});
",
"ts",
),
"run",
);
Expand All @@ -1290,7 +1311,9 @@ mod tests {
#[test]
fn define_config_arrow_empty_body() {
assert_non_static(
&parse_js_ts_config("export default defineConfig(() => {});", "ts"),
&parse(
"import { defineConfig } from 'vite-plus';\nexport default defineConfig(() => {});",
),
"run",
);
}
Expand Down
13 changes: 13 additions & 0 deletions ecosystem-ci/patch-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ if (project === 'vinext') {
throw new Error(`vinext patch: \`testTimeout: 30000\` not found in ${viteConfigPath}`);
}
await writeFile(viteConfigPath, patchedConfig, 'utf-8');

// oxlint 1.77 applies `.gitignore` to explicitly passed paths too
// (oxc-project/oxc#25133). vinext's prefer-shared-utils rule test symlinks a
// temp fixture directory into the repo and lints those files by path, and
// `.gitignore` covers the link name, so oxlint now reports "No files found to
// lint". Drop the ignore entry so the rule test keeps linting its fixtures.
const gitignorePath = join(repoRoot, '.gitignore');
const gitignore = await readFile(gitignorePath, 'utf-8');
const patchedGitignore = gitignore.replace(/^__lint_rule_fixtures__-\*$\n?/m, '');
if (patchedGitignore === gitignore) {
throw new Error(`vinext patch: \`__lint_rule_fixtures__-*\` not found in ${gitignorePath}`);
}
await writeFile(gitignorePath, patchedGitignore, 'utf-8');
}

if (project === 'dify') {
Expand Down
2 changes: 1 addition & 1 deletion ecosystem-ci/repo.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"vinext": {
"repository": "https://github.com/cloudflare/vinext.git",
"branch": "main",
"hash": "aec4421b2a95bdeb20a295a4ff391473efd724bb",
"hash": "884259a898f893138f719cbe7c66578684311e6b",
"forceFreshMigration": true
},
"reactive-resume": {
Expand Down
7 changes: 0 additions & 7 deletions packages/cli/rules/vite-tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ rule:
regex: 'vite\s+(-v|--version)'
fix: vp


# vite => vp dev (handles all cases: with/without env var prefix and flag args)
# Match command_name to preserve env var prefix and arguments
# Excludes subcommands like "vite build", "vite test", etc.
Expand All @@ -27,7 +26,6 @@ rule:
regex: 'vite\s+[^-]'
fix: vp dev


# vite <subcommand> => vp <subcommand> (handles vite build, vite test, vite dev, etc.)
# Match command_name when followed by a subcommand, replace only the command name
---
Expand All @@ -41,7 +39,6 @@ rule:
regex: 'vite\s+[^-]'
fix: vp


# oxlint => vp lint (handles all cases: with/without env var prefix and args)
# Match command_name to preserve env var prefix and arguments
---
Expand All @@ -52,7 +49,6 @@ rule:
regex: '^oxlint$'
fix: vp lint


# oxfmt => vp fmt
---
id: replace-oxfmt
Expand All @@ -62,7 +58,6 @@ rule:
regex: '^oxfmt$'
fix: vp fmt


# vitest => vp test
---
id: replace-vitest
Expand All @@ -72,7 +67,6 @@ rule:
regex: '^vitest$'
fix: vp test


# lint-staged => vp staged
---
id: replace-lint-staged
Expand All @@ -82,7 +76,6 @@ rule:
regex: '^lint-staged$'
fix: vp staged


# tsdown => vp pack
---
id: replace-tsdown
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ const commandHelpDocs = {
},
{
label: '-W, --warn=NAME',
description: 'Deny the rule or category (emit a warning)',
description: 'Warn on the rule or category (emit a warning)',
},
{
label: '-D, --deny=NAME',
Expand Down
35 changes: 34 additions & 1 deletion packages/cli/src/migration/__tests__/migrator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import os from 'node:os';
import path from 'node:path';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { parse as parseYaml } from 'yaml';
import { parseAllDocuments, parse as parseYaml } from 'yaml';

import { rewriteScripts } from '../../../binding/index.js';
import { PackageManager } from '../../types/index.js';
import { VITE_PLUS_OVERRIDE_PACKAGES, VITEST_VERSION } from '../../utils/constants.js';
import { createMigrationReport } from '../report.js';
Expand All @@ -31,6 +32,8 @@ const {
rewriteMonorepo,
rewriteMonorepoProject,
detectPendingCoreMigration,
getScriptRulesYaml,
readRulesYaml,
detectVitePlusBootstrapPending,
ensureVitePlusBootstrap,
finalizeCoreMigrationForExistingVitePlus,
Expand Down Expand Up @@ -8350,6 +8353,36 @@ describe('rewriteStandaloneProject — tsconfig types rewriting', () => {
});
});

function ruleIds(yaml: string): unknown[] {
return parseAllDocuments(yaml).map((document) => (document.toJS() as { id?: unknown })?.id);
}

describe('script rules YAML', () => {
it('keeps every rule when staged migration runs', () => {
expect(ruleIds(getScriptRulesYaml())).toContain('replace-lint-staged');
});

// Regression guard: the skip variant used to be built by splitting on blank lines, so
// a formatter that collapsed them dropped every rule and left ast-grep nothing to parse.
it('drops only the lint-staged rule when staged migration is skipped', () => {
const ids = ruleIds(getScriptRulesYaml(true));

expect(ids).toEqual(ruleIds(readRulesYaml()).filter((id) => id !== 'replace-lint-staged'));
expect(ids.every((id) => typeof id === 'string')).toBe(true);
});

it('still rewrites the remaining tools when staged migration is skipped', () => {
const scripts = { test: 'vitest', staged: 'lint-staged' };

const rewritten = rewriteScripts(JSON.stringify(scripts), getScriptRulesYaml(true));

expect(JSON.parse(rewritten!) as Record<string, string>).toEqual({
test: 'vp test',
staged: 'lint-staged',
});
});
});

describe('existing Vite+ core migration finalization', () => {
let tmpDir: string;

Expand Down
Loading
Loading