feat(isolated_declarations): add stripInternal (#5878)

closes #3906
closes #5687
closes #3958

---------

Co-authored-by: Dunqing <dengqing0821@gmail.com>
This commit is contained in:
Boshen 2024-09-19 23:14:47 +08:00 committed by GitHub
parent a07f03aab3
commit 84a5816d03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 281 additions and 38 deletions

View file

@ -3,7 +3,7 @@ use std::{env, path::Path};
use oxc_allocator::Allocator;
use oxc_codegen::{CodeGenerator, CommentOptions};
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
@ -32,7 +32,13 @@ fn main() {
println!("Original:\n");
println!("{source_text}\n");
let id_ret = IsolatedDeclarations::new(&allocator).build(&ret.program);
let id_ret = IsolatedDeclarations::new(
&allocator,
&source_text,
&ret.trivias,
IsolatedDeclarationsOptions { strip_internal: true },
)
.build(&ret.program);
let printed = CodeGenerator::new()
.enable_comment(
&source_text,

View file

@ -359,6 +359,10 @@ impl<'a> IsolatedDeclarations<'a> {
match element {
ClassElement::StaticBlock(_) => {}
ClassElement::MethodDefinition(ref method) => {
if self.has_internal_annotation(method.span) {
continue;
}
if !(method.r#type.is_abstract() || method.optional)
&& method.value.body.is_none()
{
@ -439,6 +443,10 @@ impl<'a> IsolatedDeclarations<'a> {
elements.push(new_element);
}
ClassElement::PropertyDefinition(property) => {
if self.has_internal_annotation(property.span) {
continue;
}
if self.report_property_key(&property.key, property.computed) {
continue;
}
@ -450,6 +458,10 @@ impl<'a> IsolatedDeclarations<'a> {
}
}
ClassElement::AccessorProperty(property) => {
if self.has_internal_annotation(property.span) {
continue;
}
if self.report_property_key(&property.key, property.computed) {
return None;
}
@ -476,7 +488,10 @@ impl<'a> IsolatedDeclarations<'a> {
);
elements.push(new_element);
}
ClassElement::TSIndexSignature(_) => elements.push({
ClassElement::TSIndexSignature(signature) => elements.push({
if self.has_internal_annotation(signature.span) {
continue;
}
// SAFETY: `ast.copy` is unsound! We need to fix.
unsafe { self.ast.copy(element) }
}),

View file

@ -24,13 +24,21 @@ use std::{cell::RefCell, collections::VecDeque, mem};
use diagnostics::function_with_assigning_properties;
use oxc_allocator::Allocator;
#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstBuilder, Visit, NONE};
use oxc_ast::{ast::*, AstBuilder, Trivias, Visit, NONE};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{Atom, SourceType, SPAN};
use oxc_span::{Atom, GetSpan, SourceType, SPAN};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::scope::ScopeTree;
#[derive(Debug, Clone, Copy)]
pub struct IsolatedDeclarationsOptions {
/// Do not emit declarations for code that has an @internal annotation in its JSDoc comment.
/// This is an internal compiler option; use at your own risk, because the compiler does not check that the result is valid.
/// <https://www.typescriptlang.org/tsconfig/#stripInternal>
pub strip_internal: bool,
}
pub struct IsolatedDeclarationsReturn<'a> {
pub program: Program<'a>,
pub errors: Vec<OxcDiagnostic>,
@ -38,15 +46,34 @@ pub struct IsolatedDeclarationsReturn<'a> {
pub struct IsolatedDeclarations<'a> {
ast: AstBuilder<'a>,
// state
scope: ScopeTree<'a>,
errors: RefCell<Vec<OxcDiagnostic>>,
// options
strip_internal: bool,
/// Start position of `@internal` jsdoc annotations.
internal_annotations: FxHashSet<u32>,
}
impl<'a> IsolatedDeclarations<'a> {
pub fn new(allocator: &'a Allocator) -> Self {
pub fn new(
allocator: &'a Allocator,
source_text: &str,
trivias: &Trivias,
options: IsolatedDeclarationsOptions,
) -> Self {
let strip_internal = options.strip_internal;
let is_internal_set = strip_internal
.then(|| Self::build_internal_annotations(source_text, trivias))
.unwrap_or_default();
Self {
ast: AstBuilder::new(allocator),
strip_internal,
internal_annotations: is_internal_set,
scope: ScopeTree::new(allocator),
errors: RefCell::new(vec![]),
}
@ -71,6 +98,27 @@ impl<'a> IsolatedDeclarations<'a> {
fn error(&self, error: OxcDiagnostic) {
self.errors.borrow_mut().push(error);
}
/// Build the lookup table for jsdoc `@internal`.
fn build_internal_annotations(source_text: &str, trivias: &Trivias) -> FxHashSet<u32> {
let mut set = FxHashSet::default();
for comment in trivias.comments() {
let has_internal = comment.span.source_text(source_text).contains("@internal");
// Use the first jsdoc comment if there are multiple jsdoc comments for the same node.
if has_internal && !set.contains(&comment.attached_to) {
set.insert(comment.attached_to);
}
}
set
}
/// Check if the node has an `@internal` annotation.
fn has_internal_annotation(&self, span: Span) -> bool {
if !self.strip_internal {
return false;
}
self.internal_annotations.contains(&span.start)
}
}
impl<'a> IsolatedDeclarations<'a> {
@ -104,6 +152,9 @@ impl<'a> IsolatedDeclarations<'a> {
for stmt in Self::remove_function_overloads_implementation(unsafe { self.ast.copy(stmts) })
{
if let Some(decl) = stmt.as_declaration() {
if self.has_internal_annotation(decl.span()) {
continue;
}
if let Some(decl) = self.transform_declaration(decl, false) {
new_ast_stmts.push(Statement::from(decl));
} else {
@ -138,6 +189,9 @@ impl<'a> IsolatedDeclarations<'a> {
match_declaration!(Statement) => {
match stmt.to_declaration() {
Declaration::VariableDeclaration(decl) => {
if self.has_internal_annotation(decl.span) {
continue;
}
variables_declarations.push_back(
// SAFETY: `ast.copy` is unsound! We need to fix.
unsafe { self.ast.copy(&decl.declarations) }
@ -147,6 +201,9 @@ impl<'a> IsolatedDeclarations<'a> {
variable_transformed_indexes.push_back(FxHashSet::default());
}
Declaration::TSModuleDeclaration(decl) => {
if self.has_internal_annotation(decl.span) {
continue;
}
// declare global { ... } or declare module "foo" { ... }
// We need to emit it anyway
if decl.kind.is_global() || decl.id.is_string_literal() {
@ -157,11 +214,16 @@ impl<'a> IsolatedDeclarations<'a> {
}
_ => {}
}
new_stmts.push(stmt);
if !self.has_internal_annotation(stmt.span()) {
new_stmts.push(stmt);
}
}
match_module_declaration!(Statement) => {
match stmt.to_module_declaration() {
ModuleDeclaration::ExportDefaultDeclaration(decl) => {
if self.has_internal_annotation(decl.span) {
continue;
}
transformed_indexes.insert(new_stmts.len());
if let Some((var_decl, new_decl)) =
self.transform_export_default_declaration(decl)
@ -187,6 +249,9 @@ impl<'a> IsolatedDeclarations<'a> {
}
ModuleDeclaration::ExportNamedDeclaration(decl) => {
if self.has_internal_annotation(decl.span) {
continue;
}
transformed_indexes.insert(new_stmts.len());
if let Some(new_decl) = self.transform_export_named_declaration(decl) {
self.scope.visit_declaration(
@ -205,6 +270,9 @@ impl<'a> IsolatedDeclarations<'a> {
// We must transform this in the end, because we need to know all references
}
module_declaration => {
if self.has_internal_annotation(module_declaration.span()) {
continue;
}
transformed_indexes.insert(new_stmts.len());
self.scope.visit_module_declaration(module_declaration);
}

View file

@ -1,5 +1,6 @@
use oxc_allocator::{CloneIn, Vec};
use oxc_ast::ast::{TSMethodSignatureKind, TSSignature};
use oxc_span::GetSpan;
use rustc_hash::FxHashMap;
use crate::IsolatedDeclarations;
@ -13,6 +14,9 @@ impl<'a> IsolatedDeclarations<'a> {
// <name, (requires_inference, first_param_annotation, return_type)>
let mut method_annotations: FxHashMap<_, (bool, _, _)> = FxHashMap::default();
// Strip internal signatures
signatures.retain(|signature| !self.has_internal_annotation(signature.span()));
signatures.iter_mut().for_each(|signature| {
if let TSSignature::TSMethodSignature(method) = signature {
let Some(name) = method.key.static_name() else {

View file

@ -5,20 +5,24 @@
mod tests {
use oxc_allocator::Allocator;
use oxc_codegen::CodeGenerator;
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
fn transform_dts_test(source: &str, expected: &str) {
let allocator = Allocator::default();
let source_type = SourceType::from_path("test.ts").unwrap();
let program = Parser::new(&allocator, source, source_type).parse().program;
let ret = IsolatedDeclarations::new(&allocator).build(&program);
let ret = Parser::new(&allocator, source, source_type).parse();
let ret = IsolatedDeclarations::new(
&allocator,
source,
&ret.trivias,
IsolatedDeclarationsOptions { strip_internal: true },
)
.build(&ret.program);
let actual = CodeGenerator::new().build(&ret.program).source_text;
let expected_program = Parser::new(&allocator, expected, source_type).parse().program;
let expected = CodeGenerator::new().build(&expected_program).source_text;
assert_eq!(actual.trim(), expected.trim());
}

View file

@ -0,0 +1,82 @@
/*
@internal
*/
class StripInternalClass {
public test() {
console.log("test");
}
}
class StripInternalClassFields {
/**
* @internal
*/
internalProperty: string = "internal";
// @internal
internalMethod(): void {}
}
/**
@internal
*/
function stripInternalFunction() {
console.log("test");
}
export { stripInternalFunction, StripInternalClass, StripInternalClassFields };
/**
@internal
*/
export function stripInternalExportedFunction() {
console.log("test");
}
/**
@internal*/
export const stripInternalExportedConst = "test";
/**
@internal*/
export interface StripInternalExportedInterface {}
export interface StripInternalInterfaceSignatures {
/**
* @internal
*/
internalMethod(): void;
/**
* @internal
*/
internalProperty: number;
/**@internal */
new (): any;
}
export type StripInternalTypeSignatures = {
/**
* @internal
*/
internalMethod(): void;
/**
* @internal
*/
internalProperty: number;
/**@internal */
new (): any;
};
export namespace StripInternalNamespaceInner {
/**
* @internal
*/
export function internalFunction() {
console.log("test");
}
}
/**
* @internal
*/
export namespace StripInternalNamespace {}

View file

@ -4,7 +4,7 @@ use std::{fs, path::Path, sync::Arc};
use oxc_allocator::Allocator;
use oxc_codegen::{CodeGenerator, CommentOptions};
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
@ -13,7 +13,13 @@ fn transform(path: &Path, source_text: &str) -> String {
let source_type = SourceType::from_path(path).unwrap();
let parser_ret = Parser::new(&allocator, source_text, source_type).parse();
let id_ret = IsolatedDeclarations::new(&allocator).build(&parser_ret.program);
let id_ret = IsolatedDeclarations::new(
&allocator,
source_text,
&parser_ret.trivias,
IsolatedDeclarationsOptions { strip_internal: true },
)
.build(&parser_ret.program);
let code = CodeGenerator::new()
.enable_comment(
source_text,

View file

@ -0,0 +1,13 @@
---
source: crates/oxc_isolated_declarations/tests/mod.rs
input_file: crates/oxc_isolated_declarations/tests/fixtures/strip-internal.ts
---
==================== .D.TS ====================
declare class StripInternalClassFields {}
export { stripInternalFunction, StripInternalClass, StripInternalClassFields };
export interface StripInternalInterfaceSignatures {}
export type StripInternalTypeSignatures = {};
export declare namespace StripInternalNamespaceInner {
export {};
}

View file

@ -21,6 +21,15 @@ export interface Es2015BindingOptions {
export declare function isolatedDeclaration(filename: string, sourceText: string, options?: IsolatedDeclarationsOptions | undefined | null): IsolatedDeclarationsResult
export interface IsolatedDeclarationsOptions {
/**
* Do not emit declarations for code that has an @internal annotation in its JSDoc comment.
* This is an internal compiler option; use at your own risk, because the compiler does not check that the result is valid.
*
* Default: `false`
*
* See <https://www.typescriptlang.org/tsconfig/#stripInternal>
*/
stripInternal?: boolean
sourcemap?: boolean
}
@ -251,7 +260,7 @@ export interface TypeScriptBindingOptions {
*
* @default false
*/
declaration?: boolean
declaration?: IsolatedDeclarationsOptions
/**
* Rewrite or remove TypeScript import/export declaration extensions.
*

View file

@ -11,7 +11,7 @@ use oxc_diagnostics::{Error, NamedSource, OxcDiagnostic};
use oxc_parser::{Parser, ParserReturn};
use oxc_span::SourceType;
use crate::TransformOptions;
use crate::{IsolatedDeclarationsOptions, TransformOptions};
#[must_use]
pub(crate) struct TransformContext<'a> {
@ -27,7 +27,7 @@ pub(crate) struct TransformContext<'a> {
/// Generate `.d.ts` files?
///
/// Used by [`crate::transform`].
declarations: bool,
declarations: Option<IsolatedDeclarationsOptions>,
/// Path to the file being transformed.
filename: &'a str,
@ -53,11 +53,8 @@ impl<'a> TransformContext<'a> {
// Options that are added by this napi crates and don't exist in
// oxc_transformer.
let source_map = options.as_ref().and_then(|o| o.sourcemap).unwrap_or_default();
let declarations = options
.as_ref()
.and_then(|o| o.typescript.as_ref())
.and_then(|t| t.declaration)
.unwrap_or_default();
let declarations =
options.as_ref().and_then(|o| o.typescript.as_ref()).and_then(|t| t.declaration);
// Insert options into the cell if provided. Otherwise they will be
// initialized to default when first accessed.
@ -103,8 +100,8 @@ impl<'a> TransformContext<'a> {
}
#[inline]
pub(crate) fn declarations(&self) -> bool {
self.declarations
pub(crate) fn declarations(&self) -> Option<&IsolatedDeclarationsOptions> {
self.declarations.as_ref()
}
#[inline]

View file

@ -13,9 +13,17 @@ pub struct IsolatedDeclarationsResult {
pub errors: Vec<String>,
}
#[derive(Debug, Default)]
#[napi(object)]
#[derive(Debug, Default, Clone, Copy)]
pub struct IsolatedDeclarationsOptions {
/// Do not emit declarations for code that has an @internal annotation in its JSDoc comment.
/// This is an internal compiler option; use at your own risk, because the compiler does not check that the result is valid.
///
/// Default: `false`
///
/// See <https://www.typescriptlang.org/tsconfig/#stripInternal>
pub strip_internal: Option<bool>,
pub sourcemap: Option<bool>,
}
@ -37,7 +45,7 @@ pub fn isolated_declaration(
source_type,
Some(TransformOptions { sourcemap: options.sourcemap, ..Default::default() }),
);
let transformed_ret = build_declarations(&ctx);
let transformed_ret = build_declarations(&ctx, options);
IsolatedDeclarationsResult {
code: transformed_ret.source_text,
@ -46,8 +54,19 @@ pub fn isolated_declaration(
}
}
pub(crate) fn build_declarations(ctx: &TransformContext<'_>) -> CodegenReturn {
let transformed_ret = IsolatedDeclarations::new(ctx.allocator).build(&ctx.program());
pub(crate) fn build_declarations(
ctx: &TransformContext<'_>,
options: IsolatedDeclarationsOptions,
) -> CodegenReturn {
let transformed_ret = IsolatedDeclarations::new(
ctx.allocator,
ctx.source_text(),
&ctx.trivias,
oxc_isolated_declarations::IsolatedDeclarationsOptions {
strip_internal: options.strip_internal.unwrap_or(false),
},
)
.build(&ctx.program());
ctx.add_diagnostics(transformed_ret.errors);
ctx.codegen()
.enable_comment(

View file

@ -9,6 +9,8 @@ use oxc_transformer::{
RewriteExtensionsMode, TypeScriptOptions,
};
use crate::IsolatedDeclarationsOptions;
#[napi(object)]
#[derive(Default)]
pub struct TypeScriptBindingOptions {
@ -24,7 +26,7 @@ pub struct TypeScriptBindingOptions {
/// requirements.
///
/// @default false
pub declaration: Option<bool>,
pub declaration: Option<IsolatedDeclarationsOptions>,
/// Rewrite or remove TypeScript import/export declaration extensions.
///
/// - When set to `rewrite`, it will change `.ts`, `.mts`, `.cts` extensions to `.js`, `.mjs`, `.cjs` respectively.

View file

@ -80,9 +80,11 @@ pub fn transform(
let allocator = Allocator::default();
let ctx = TransformContext::new(&allocator, &filename, &source_text, source_type, options);
let should_build_types = ctx.declarations() && source_type.is_typescript();
let declarations_result =
should_build_types.then(|| isolated_declaration::build_declarations(&ctx));
let declarations_result = source_type
.is_typescript()
.then(|| ctx.declarations())
.flatten()
.map(|options| isolated_declaration::build_declarations(&ctx, *options));
let transpile_result = transpile(&ctx);

View file

@ -1,6 +1,6 @@
use oxc_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_isolated_declarations::IsolatedDeclarations;
use oxc_isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions};
use oxc_parser::{Parser, ParserReturn};
use oxc_span::SourceType;
use oxc_tasks_common::TestFile;
@ -18,9 +18,15 @@ fn bench_isolated_declarations(criterion: &mut Criterion) {
group.bench_with_input(id, &file.source_text, |b, source_text| {
b.iter_with_large_drop(|| {
let allocator = Allocator::default();
let ParserReturn { program, .. } =
let ParserReturn { program, trivias, .. } =
Parser::new(&allocator, source_text, source_type).parse();
IsolatedDeclarations::new(&allocator).build(&program);
IsolatedDeclarations::new(
&allocator,
source_text,
&trivias,
IsolatedDeclarationsOptions { strip_internal: true },
)
.build(&program);
});
});

View file

@ -3,8 +3,12 @@
use std::path::{Path, PathBuf};
use oxc::{
allocator::Allocator, codegen::CodeGenerator, diagnostics::OxcDiagnostic,
isolated_declarations::IsolatedDeclarations, parser::Parser, span::SourceType,
allocator::Allocator,
codegen::CodeGenerator,
diagnostics::OxcDiagnostic,
isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions},
parser::Parser,
span::SourceType,
};
use super::{
@ -174,7 +178,13 @@ 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 = IsolatedDeclarations::new(&allocator).build(&ret.program);
let ret = IsolatedDeclarations::new(
&allocator,
source_text,
&ret.trivias,
IsolatedDeclarationsOptions { strip_internal: true },
)
.build(&ret.program);
let printed = CodeGenerator::new().build(&ret.program).source_text;
(printed, ret.errors)
}