feat(napi): isolated-declaration (#3718)

This commit is contained in:
Boshen 2024-06-17 13:06:00 +00:00
parent 1c7f19c868
commit 0b8098a442
24 changed files with 531 additions and 48 deletions

16
Cargo.lock generated
View file

@ -1431,6 +1431,22 @@ dependencies = [
"rustc-hash",
]
[[package]]
name = "oxc_isolated_declarations_napi"
version = "0.0.0"
dependencies = [
"napi",
"napi-build",
"napi-derive",
"oxc_allocator",
"oxc_ast",
"oxc_codegen",
"oxc_diagnostics",
"oxc_isolated_declarations",
"oxc_parser",
"oxc_span",
]
[[package]]
name = "oxc_js_regex"
version = "0.0.0"

View file

@ -4,7 +4,7 @@ use std::{env, path::Path};
use oxc_allocator::Allocator;
use oxc_ast::Trivias;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_isolated_declarations::TransformerDts;
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_parser::Parser;
use oxc_span::SourceType;
@ -33,7 +33,7 @@ fn main() {
println!("Original:\n");
println!("{source_text}\n");
let ret = TransformerDts::new(&allocator).build(&ret.program);
let ret = IsolatedDeclarations::new(&allocator).build(&ret.program);
let printed =
Codegen::<false>::new("", &source_text, Trivias::default(), CodegenOptions::default())
.build(&ret.program)

View file

@ -6,10 +6,10 @@ use oxc_span::{GetSpan, SPAN};
use crate::{
diagnostics::{computed_property_name, extends_clause_expression},
TransformerDts,
IsolatedDeclarations,
};
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn is_literal_key(&self, key: &PropertyKey<'a>) -> bool {
match key {
PropertyKey::StringLiteral(_)

View file

@ -7,9 +7,9 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{GetSpan, SPAN};
use oxc_syntax::scope::ScopeFlags;
use crate::{diagnostics::signature_computed_property_name, TransformerDts};
use crate::{diagnostics::signature_computed_property_name, IsolatedDeclarations};
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_variable_declaration(
&self,
decl: &VariableDeclaration<'a>,
@ -262,7 +262,7 @@ impl<'a> TransformerDts<'a> {
}
}
impl<'a> Visit<'a> for TransformerDts<'a> {
impl<'a> Visit<'a> for IsolatedDeclarations<'a> {
fn visit_ts_method_signature(&mut self, signature: &TSMethodSignature<'a>) {
self.report_signature_property_key(&signature.key, signature.computed);
}

View file

@ -8,7 +8,7 @@ use oxc_syntax::{
};
use rustc_hash::FxHashMap;
use crate::{diagnostics::enum_member_initializers, TransformerDts};
use crate::{diagnostics::enum_member_initializers, IsolatedDeclarations};
#[derive(Debug, Clone)]
enum ConstantValue {
@ -16,7 +16,7 @@ enum ConstantValue {
String(String),
}
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_ts_enum_declaration(
&mut self,
decl: &TSEnumDeclaration<'a>,

View file

@ -6,9 +6,9 @@ use oxc_ast::ast::Function;
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::SPAN;
use crate::TransformerDts;
use crate::IsolatedDeclarations;
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_function(&mut self, func: &Function<'a>) -> Option<Box<'a, Function<'a>>> {
if func.modifiers.is_contains_declare() {
None

View file

@ -8,10 +8,10 @@ use oxc_span::{GetSpan, SPAN};
use crate::{
diagnostics::function_must_have_explicit_return_type, return_type::FunctionReturnType,
TransformerDts,
IsolatedDeclarations,
};
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn infer_type_from_expression(&self, expr: &Expression<'a>) -> Option<TSType<'a>> {
match expr {
Expression::BooleanLiteral(_) => Some(self.ctx.ast.ts_boolean_keyword(SPAN)),

View file

@ -27,17 +27,17 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{SourceType, SPAN};
use scope::ScopeTree;
pub struct TransformerDtsReturn<'a> {
pub struct IsolatedDeclarationsReturn<'a> {
pub program: Program<'a>,
pub errors: Vec<OxcDiagnostic>,
}
pub struct TransformerDts<'a> {
pub struct IsolatedDeclarations<'a> {
ctx: Ctx<'a>,
scope: ScopeTree<'a>,
}
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn new(allocator: &'a Allocator) -> Self {
let ctx = Rc::new(TransformDtsCtx::new(allocator));
Self { ctx, scope: ScopeTree::new(allocator) }
@ -46,16 +46,16 @@ impl<'a> TransformerDts<'a> {
/// # Errors
///
/// Returns `Vec<Error>` if any errors were collected during the transformation.
pub fn build(mut self, program: &Program<'a>) -> TransformerDtsReturn<'a> {
pub fn build(mut self, program: &Program<'a>) -> IsolatedDeclarationsReturn<'a> {
let source_type = SourceType::default().with_module(true).with_typescript_definition(true);
let directives = self.ctx.ast.new_vec();
let stmts = self.transform_program(program);
let program = self.ctx.ast.program(SPAN, source_type, directives, None, stmts);
TransformerDtsReturn { program, errors: self.ctx.take_errors() }
IsolatedDeclarationsReturn { program, errors: self.ctx.take_errors() }
}
}
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_program(
&mut self,
program: &Program<'a>,

View file

@ -5,9 +5,9 @@ use oxc_allocator::Box;
use oxc_ast::Visit;
use oxc_span::{GetSpan, SPAN};
use crate::TransformerDts;
use crate::IsolatedDeclarations;
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_export_named_declaration(
&mut self,
decl: &ExportNamedDeclaration<'a>,

View file

@ -10,7 +10,7 @@ use oxc_ast::{
use oxc_span::{Atom, GetSpan};
use oxc_syntax::scope::ScopeFlags;
use crate::{context::Ctx, diagnostics::type_containing_private_name, TransformerDts};
use crate::{context::Ctx, diagnostics::type_containing_private_name, IsolatedDeclarations};
/// Infer return type from return statement. Does not support multiple return statements.
pub struct FunctionReturnType<'a> {
@ -23,7 +23,10 @@ pub struct FunctionReturnType<'a> {
}
impl<'a> FunctionReturnType<'a> {
pub fn infer(transformer: &TransformerDts<'a>, body: &FunctionBody<'a>) -> Option<TSType<'a>> {
pub fn infer(
transformer: &IsolatedDeclarations<'a>,
body: &FunctionBody<'a>,
) -> Option<TSType<'a>> {
let mut visitor = FunctionReturnType {
ctx: Rc::clone(&transformer.ctx),
return_expression: None,

View file

@ -6,9 +6,9 @@ use oxc_ast::ast::{
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::SPAN;
use crate::TransformerDts;
use crate::IsolatedDeclarations;
impl<'a> TransformerDts<'a> {
impl<'a> IsolatedDeclarations<'a> {
pub fn transform_function_to_ts_type(&self, func: &Function<'a>) -> Option<TSType<'a>> {
let return_type = self.infer_function_return_type(func);
let params = self.transform_formal_parameters(&func.params);

2
napi/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
*.node

View file

@ -0,0 +1,39 @@
[package]
name = "oxc_isolated_declarations_napi"
version = "0.0.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
categories.workspace = true
[lints]
workspace = true
[lib]
crate-type = ["cdylib"]
test = false
doctest = false
[dependencies]
oxc_allocator = { workspace = true }
oxc_parser = { workspace = true }
oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_codegen = { workspace = true }
oxc_isolated_declarations = { workspace = true }
napi = { workspace = true }
napi-derive = { workspace = true }
[package.metadata.cargo-shear]
ignored = ["napi"]
[build-dependencies]
napi-build = { workspace = true }

View file

@ -0,0 +1,3 @@
fn main() {
napi_build::setup();
}

11
napi/isolated-declarations/index.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
export interface IsolatedDeclarationsResult {
sourceText: string
errors: Array<string>
}
/** TypeScript Isolated Declarations for Standalone DTS Emit */
export function isolatedDeclaration(filename: string, sourceText: string): IsolatedDeclarationsResult

View file

@ -0,0 +1,315 @@
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
const lddPath = require('child_process').execSync('which ldd').toString().trim()
return readFileSync(lddPath, 'utf8').includes('musl')
} catch (e) {
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !glibcVersionRuntime
}
}
switch (platform) {
case 'android':
switch (arch) {
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'isolated-declarations.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.android-arm64.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'isolated-declarations.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.android-arm-eabi.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.win32-x64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.win32-x64-msvc.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.win32-ia32-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.win32-ia32-msvc.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.win32-arm64-msvc.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.win32-arm64-msvc.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
localFileExisted = existsSync(join(__dirname, 'isolated-declarations.darwin-universal.node'))
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.darwin-universal.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-darwin-universal')
}
break
} catch {}
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'isolated-declarations.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.darwin-x64.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.darwin-arm64.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.darwin-arm64.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'isolated-declarations.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.freebsd-x64.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-x64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-x64-musl.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-x64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-x64-gnu.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-arm64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-arm64-musl.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-arm64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-arm64-gnu.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-arm-musleabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-arm-musleabihf.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-arm-musleabihf')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-arm-gnueabihf.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
}
break
case 'riscv64':
if (isMusl()) {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-riscv64-musl.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-riscv64-musl.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-riscv64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-riscv64-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-riscv64-gnu.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-riscv64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 's390x':
localFileExisted = existsSync(
join(__dirname, 'isolated-declarations.linux-s390x-gnu.node')
)
try {
if (localFileExisted) {
nativeBinding = require('./isolated-declarations.linux-s390x-gnu.node')
} else {
nativeBinding = require('@oxc-isolated-declarations/binding-linux-s390x-gnu')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { isolatedDeclaration } = nativeBinding
module.exports.isolatedDeclaration = isolatedDeclaration

View file

@ -0,0 +1,30 @@
{
"name": "@oxc-isolated-declarations/binding",
"private": true,
"scripts": {
"build": "napi build --platform --release",
"test": "node test.mjs"
},
"devDependencies": {
"@napi-rs/cli": "^2.18.0"
},
"engines": {
"node": ">=14.*"
},
"napi": {
"name": "isolated-declarations",
"triples": {
"defaults": false,
"additional": [
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl",
"x86_64-apple-darwin",
"aarch64-apple-darwin"
]
}
}
}

View file

@ -0,0 +1,24 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@napi-rs/cli':
specifier: ^2.18.0
version: 2.18.3
packages:
'@napi-rs/cli@2.18.3':
resolution: {integrity: sha512-L0f4kP0dyG8W5Qtc7MtP73VvLLrOLyRcUEBzknIfu8Jk4Jfhrsx1ItMHgyalYqMSslWdY3ojEfAaU5sx1VyeQQ==}
engines: {node: '>= 10'}
hasBin: true
snapshots:
'@napi-rs/cli@2.18.3': {}

View file

@ -0,0 +1,49 @@
use napi_derive::napi;
use oxc_allocator::Allocator;
use oxc_ast::Trivias;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_diagnostics::{Error, NamedSource};
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_parser::Parser;
use oxc_span::SourceType;
use std::sync::Arc;
#[napi(object)]
pub struct IsolatedDeclarationsResult {
pub source_text: String,
pub errors: Vec<String>,
}
/// TypeScript Isolated Declarations for Standalone DTS Emit
#[allow(clippy::needless_pass_by_value)]
#[napi]
pub fn isolated_declaration(filename: String, source_text: String) -> IsolatedDeclarationsResult {
let source_type = SourceType::from_path(&filename).unwrap_or_default().with_typescript(true);
let allocator = Allocator::default();
let parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
let transformed_ret = IsolatedDeclarations::new(&allocator).build(&parser_ret.program);
let printed = Codegen::<false>::new(
&filename,
&source_text,
Trivias::default(),
CodegenOptions::default(),
)
.build(&transformed_ret.program)
.source_text;
let mut errors = vec![];
if !parser_ret.errors.is_empty() || !transformed_ret.errors.is_empty() {
let source = Arc::new(NamedSource::new(filename, source_text.to_string()));
errors.extend(
parser_ret
.errors
.into_iter()
.chain(transformed_ret.errors)
.map(|diagnostic| Error::from(diagnostic).with_source_code(Arc::clone(&source)))
.map(|error| format!("{error:?}")),
);
}
IsolatedDeclarationsResult { source_text: printed, errors }
}

View file

@ -0,0 +1,12 @@
import oxc from './index.js';
import assert from 'assert';
console.log(`Testing on ${process.platform}-${process.arch}`)
test(oxc.isolatedDeclaration("test.ts", "class A {}"), "declare class A {}\n");
function test(ret, expected) {
console.log(ret);
assert.equal(ret.sourceText, expected);
assert(ret.errors.length == 0);
}

View file

@ -1,2 +0,0 @@
/node_modules/
*.node

View file

@ -1,18 +0,0 @@
# Installation
```bash
corepack enable
```
# Build
```bash
pnpm install
pnpm run build
```
# Test
```bash
pnpm test
```

View file

@ -14,7 +14,6 @@
"engines": {
"node": ">=14.*"
},
"packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2",
"napi": {
"name": "parser",
"triples": {

View file

@ -6,7 +6,7 @@ use oxc_allocator::Allocator;
use oxc_ast::Trivias;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_diagnostics::OxcDiagnostic;
use oxc_isolated_declarations::TransformerDts;
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_parser::Parser;
use oxc_span::SourceType;
@ -166,7 +166,7 @@ fn transpile(path: &Path, source_text: &str) -> (String, Vec<OxcDiagnostic>) {
let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let ret = TransformerDts::new(&allocator).build(&ret.program);
let ret = IsolatedDeclarations::new(&allocator).build(&ret.program);
let printed = Codegen::<false>::new("", "", Trivias::default(), CodegenOptions::default())
.build(&ret.program)
.source_text;