diff --git a/apps/wing/fixtures/valid1/API.md b/apps/wing/fixtures/valid1/API.md new file mode 100644 index 00000000000..a5882b101a6 --- /dev/null +++ b/apps/wing/fixtures/valid1/API.md @@ -0,0 +1,25 @@ +

API Reference

+ +

Table of Contents

+ +- **Classes** + - Foo + +

Foo (preflight class)

+ +A test class + +

Constructor

+ +
+new(): Foo
+
+ +

Properties

+ +*No properties* + +

Methods

+ +*No methods* + diff --git a/apps/wing/fixtures/valid1/lib.w b/apps/wing/fixtures/valid1/lib.w index e69de29bb2d..040ceacd612 100644 --- a/apps/wing/fixtures/valid1/lib.w +++ b/apps/wing/fixtures/valid1/lib.w @@ -0,0 +1,2 @@ +/// A test class +pub class Foo {} diff --git a/apps/wing/src/cli.ts b/apps/wing/src/cli.ts index b53553c3148..5a86547c92b 100644 --- a/apps/wing/src/cli.ts +++ b/apps/wing/src/cli.ts @@ -164,6 +164,12 @@ async function main() { .hook("preAction", collectAnalyticsHook) .action(runSubCommand("lsp")); + program + .command("gen-docs") + .description("Generate documentation for the current project") + .hook("preAction", collectAnalyticsHook) + .action(runSubCommand("generateDocs")); + program .command("compile") .description("Compiles a Wing program") diff --git a/apps/wing/src/commands/compile.ts b/apps/wing/src/commands/compile.ts index 67277344e3c..56068613345 100644 --- a/apps/wing/src/commands/compile.ts +++ b/apps/wing/src/commands/compile.ts @@ -1,13 +1,13 @@ -import { promises as fsPromise } from "fs"; -import { dirname, relative, resolve } from "path"; +import { dirname, resolve } from "path"; import * as wingCompiler from "@winglang/compiler"; import { loadEnvVariables } from "@winglang/sdk/lib/helpers"; import { prettyPrintError } from "@winglang/sdk/lib/util/enhanced-error"; import chalk from "chalk"; -import { CHARS_ASCII, emitDiagnostic, File, Label } from "codespan-wasm"; import debug from "debug"; import { glob } from "glob"; +import { formatDiagnostics } from "./diagnostics"; +import { COLORING } from "../util"; // increase the stack trace limit to 50, useful for debugging Rust panics // (not setting the limit too high in case of infinite recursion) @@ -88,80 +88,21 @@ export async function compile(entrypoint?: string, options?: CompileOptions): Pr modes: options?.testing ? ["test"] : ["compile"], cwd: resolve(dirname(entrypoint)), }); - const coloring = chalk.supportsColor ? chalk.supportsColor.hasBasic : false; const compileOutput = await wingCompiler.compile(entrypoint, { ...options, log, - color: coloring, + color: COLORING, platform: options?.platform ?? ["sim"], }); if (compileOutput.wingcErrors.length > 0) { // Print any errors or warnings from the compiler. const diagnostics = compileOutput.wingcErrors; - const cwd = process.cwd(); - const result = []; - - for (const diagnostic of diagnostics) { - const { message, span, annotations, hints, severity } = diagnostic; - const files: File[] = []; - const labels: Label[] = []; - - // file_id might be "" if the span is synthetic (see #2521) - if (span?.file_id) { - // `span` should only be null if source file couldn't be read etc. - const source = await fsPromise.readFile(span.file_id, "utf8"); - const start = span.start_offset; - const end = span.end_offset; - const filePath = relative(cwd, span.file_id); - files.push({ name: filePath, source }); - labels.push({ - fileId: filePath, - rangeStart: start, - rangeEnd: end, - message: "", - style: "primary", - }); - } - - for (const annotation of annotations) { - // file_id might be "" if the span is synthetic (see #2521) - if (!annotation.span?.file_id) { - continue; - } - const source = await fsPromise.readFile(annotation.span.file_id, "utf8"); - const start = annotation.span.start_offset; - const end = annotation.span.end_offset; - const filePath = relative(cwd, annotation.span.file_id); - files.push({ name: filePath, source }); - labels.push({ - fileId: filePath, - rangeStart: start, - rangeEnd: end, - message: annotation.message, - style: "secondary", - }); - } - - const diagnosticText = emitDiagnostic( - files, - { - message, - severity, - labels, - notes: hints.map((hint) => `hint: ${hint}`), - }, - { - chars: CHARS_ASCII, - }, - coloring - ); - result.push(diagnosticText); - } + const formatted = await formatDiagnostics(diagnostics); if (compileOutput.wingcErrors.map((e) => e.severity).includes("error")) { - throw new Error(result.join("\n").trimEnd()); + throw new Error(formatted); } else { - console.error(result.join("\n").trimEnd()); + console.error(formatted); } } diff --git a/apps/wing/src/commands/diagnostics.ts b/apps/wing/src/commands/diagnostics.ts new file mode 100644 index 00000000000..eef6756cfb1 --- /dev/null +++ b/apps/wing/src/commands/diagnostics.ts @@ -0,0 +1,69 @@ +import { readFile } from "fs/promises"; +import { relative } from "path"; +import { WingDiagnostic } from "@winglang/compiler"; +import { Label, File, emitDiagnostic, CHARS_ASCII } from "codespan-wasm"; +import { COLORING } from "../util"; + +export async function formatDiagnostics(diagnostics: WingDiagnostic[]): Promise { + const cwd = process.cwd(); + const result = []; + + for (const diagnostic of diagnostics) { + const { message, span, annotations, hints, severity } = diagnostic; + const files: File[] = []; + const labels: Label[] = []; + + // file_id might be "" if the span is synthetic (see #2521) + if (span?.file_id) { + // `span` should only be null if source file couldn't be read etc. + const source = await readFile(span.file_id, "utf8"); + const start = span.start_offset; + const end = span.end_offset; + const filePath = relative(cwd, span.file_id); + files.push({ name: filePath, source }); + labels.push({ + fileId: filePath, + rangeStart: start, + rangeEnd: end, + message: "", + style: "primary", + }); + } + + for (const annotation of annotations) { + // file_id might be "" if the span is synthetic (see #2521) + if (!annotation.span?.file_id) { + continue; + } + const source = await readFile(annotation.span.file_id, "utf8"); + const start = annotation.span.start_offset; + const end = annotation.span.end_offset; + const filePath = relative(cwd, annotation.span.file_id); + files.push({ name: filePath, source }); + labels.push({ + fileId: filePath, + rangeStart: start, + rangeEnd: end, + message: annotation.message, + style: "secondary", + }); + } + + const diagnosticText = emitDiagnostic( + files, + { + message, + severity, + labels, + notes: hints.map((hint) => `hint: ${hint}`), + }, + { + chars: CHARS_ASCII, + }, + COLORING + ); + result.push(diagnosticText); + } + + return result.join("\n").trimEnd(); +} diff --git a/apps/wing/src/commands/generateDocs.test.ts b/apps/wing/src/commands/generateDocs.test.ts new file mode 100644 index 00000000000..0f241a5beba --- /dev/null +++ b/apps/wing/src/commands/generateDocs.test.ts @@ -0,0 +1,49 @@ +import * as fs from "fs/promises"; +import { basename, join } from "path"; +import { describe, it, expect, afterEach, vi } from "vitest"; +import { generateDocs } from "./generateDocs"; + +const fixturesDir = join(__dirname, "..", "..", "fixtures"); + +console.log = vi.fn(); + +describe("wing gen-docs", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("throws an error if a project is missing package.json", async () => { + // GIVEN + const projectDir = join(fixturesDir, "invalid1"); + process.chdir(projectDir); + + // THEN + await expect(generateDocs()).rejects.toThrow("No package.json found in project directory"); + await expectNoDocs(projectDir); + }); + + it("generates docs for a Wing library", async () => { + // GIVEN + const projectDir = join(fixturesDir, "valid1"); + process.chdir(projectDir); + + await fs.rm(join(projectDir, "API.md")); + + // WHEN + await generateDocs(); + + // THEN + const files = await fs.readdir(projectDir); + expect(files.findIndex((path) => basename(path) === "API.md")).not.toEqual(-1); + const apiMd = await fs.readFile(join(projectDir, "API.md"), "utf8"); + expect(apiMd).toContain("Foo"); + }); +}); + +/** + * Asserts that no docs were created in the specified directory. + */ +async function expectNoDocs(projectDir: string) { + const files = await fs.readdir(projectDir); + expect(files.findIndex((path) => basename(path) === "API.md")).toEqual(-1); +} diff --git a/apps/wing/src/commands/generateDocs.ts b/apps/wing/src/commands/generateDocs.ts new file mode 100644 index 00000000000..5efb98d993d --- /dev/null +++ b/apps/wing/src/commands/generateDocs.ts @@ -0,0 +1,34 @@ +import { writeFile } from "fs/promises"; +import { relative, resolve } from "path"; +import * as wingCompiler from "@winglang/compiler"; +import chalk from "chalk"; +import debug from "debug"; +import { formatDiagnostics } from "./diagnostics"; + +const log = debug("wing:generateDocs"); +const color = chalk.supportsColor ? chalk.supportsColor.hasBasic : false; + +export async function generateDocs() { + // TODO: calculate the workDir by looking up for a wing.toml file + // For now, assume the workDir is the current directory + const workDir = "."; + + const docs = await wingCompiler.generateWingDocs({ + projectDir: workDir, + color, + log, + }); + + if (docs.diagnostics.length > 0) { + if (docs.diagnostics.some((d) => d.severity === "error")) { + throw new Error(await formatDiagnostics(docs.diagnostics)); + } else { + console.error(await formatDiagnostics(docs.diagnostics)); + } + } + + const docsFile = resolve(workDir, "API.md"); + await writeFile(docsFile, docs.docsContents); + + console.log(`Docs generated at ${relative(".", docsFile)}.`); +} diff --git a/apps/wing/src/util.ts b/apps/wing/src/util.ts index d94ac820f9a..d5504c63b17 100644 --- a/apps/wing/src/util.ts +++ b/apps/wing/src/util.ts @@ -4,9 +4,12 @@ import * as fs from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { promisify } from "util"; +import chalk from "chalk"; export const DEFAULT_PARALLEL_SIZE = 10; +export const COLORING = chalk.supportsColor ? chalk.supportsColor.hasBasic : false; + /** * Normalize windows paths to be posix-like. */ diff --git a/docs/api/02-cli-user-manual.md b/docs/api/02-cli-user-manual.md index 7119a5fe597..5bbd32f167b 100644 --- a/docs/api/02-cli-user-manual.md +++ b/docs/api/02-cli-user-manual.md @@ -242,7 +242,17 @@ This will compile your current Wing directory, and bundle it as a tarball that c See [Libraries](/docs/category/wing-libraries-winglibs) for more details on packaging and consuming Wing libraries. -::: +## Generate Docs: `wing gen-docs` + +The `wing gen-docs` command can be used to generate API documentation for your Wing project. + +Usage: + +```sh +$ wing gen-docs +``` + +This will generate a file named `API.md` in the root of your project. ## Store Secrets: `wing secrets` diff --git a/examples/wing-fixture/API.md b/examples/wing-fixture/API.md new file mode 100644 index 00000000000..eb3329b5a17 --- /dev/null +++ b/examples/wing-fixture/API.md @@ -0,0 +1,109 @@ +

API Reference

+ +

Table of Contents

+ +- **Classes** + - PublicClass + - Store + - subdir.Util +- **Interfaces** + - PublicInterface +- **Structs** + - PublicStruct + - StoreOptions +- **Enums** + - FavoriteNumbers + +

PublicClass (preflight class)

+ +

Constructor

+ +
+new(): PublicClass
+
+ +

Properties

+ +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| publicField | num | *No description* | + +

Methods

+ +| **Signature** | **Description** | +| --- | --- | +| publicMethod(): void | *No description* | + +

Store (preflight class)

+ +

Constructor

+ +
+new(options: StoreOptions?): Store
+
+ +

Properties

+ +*No properties* + +

Methods

+ +| **Signature** | **Description** | +| --- | --- | +| static makeKey(name: str): str | *No description* | +| static inflight makeKeyInflight(name: str): str | *No description* | +| onSet(handler: inflight (str): void): void | *No description* | +| inflight set(message: str): void | *No description* | + +

subdir.Util (preflight class)

+ +

Constructor

+ +
+new(): Util
+
+ +

Properties

+ +*No properties* + +

Methods

+ +| **Signature** | **Description** | +| --- | --- | +| static inflight double(msg: str): str | *No description* | +| static inflight makeKeyInflight(name: str): str | *No description* | + +

PublicInterface (interface)

+ +

Properties

+ +*No properties* + +

Methods

+ +*No methods* + +

PublicStruct (struct)

+ +

Properties

+ +*No properties* + +

StoreOptions (struct)

+ +

Properties

+ +| **Name** | **Type** | **Description** | +| --- | --- | --- | +| name | str? | *No description* | + +

FavoriteNumbers (enum)

+ +

Values

+ +| **Name** | **Description** | +| --- | --- | +| SEVEN | *No description* | +| FORTY_TWO | *No description* | + diff --git a/examples/wing-fixture/package.json b/examples/wing-fixture/package.json index 858042dd742..193b398fc3e 100644 --- a/examples/wing-fixture/package.json +++ b/examples/wing-fixture/package.json @@ -10,7 +10,7 @@ }, "scripts": { "compile": "wing compile .", - "test": "wing test" + "test": "wing test && wing gen-docs" }, "license": "MIT", "engines": { diff --git a/libs/wingc/examples/generate_docs.rs b/libs/wingc/examples/generate_docs.rs new file mode 100644 index 00000000000..c07f9b7a90a --- /dev/null +++ b/libs/wingc/examples/generate_docs.rs @@ -0,0 +1,30 @@ +// Compiles a project directory and generates docs for it. + +use camino::Utf8Path; +use std::{env, process}; +use wingc::{diagnostic::get_diagnostics, generate_docs::generate_docs}; + +pub fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + panic!("Usage: cargo run --example generate_docs "); + } + + let project_dir = Utf8Path::new(&args[1]).canonicalize_utf8().unwrap(); + + let docs = generate_docs(&project_dir); + let mut diags = get_diagnostics(); + if !diags.is_empty() { + // Sort error messages by line number (ascending) + diags.sort(); + eprintln!( + "Compilation failed with {} errors\n{}", + diags.len(), + diags.iter().map(|d| format!("{}", d)).collect::>().join("\n") + ); + process::exit(1); + } + + println!("{}", docs.unwrap()); +} diff --git a/libs/wingc/src/comp_ctx.rs b/libs/wingc/src/comp_ctx.rs index 45952db0b73..5ef5b49997c 100644 --- a/libs/wingc/src/comp_ctx.rs +++ b/libs/wingc/src/comp_ctx.rs @@ -65,20 +65,19 @@ impl CompilationContext { } pub fn set_custom_panic_hook() { - std::panic::set_hook(Box::new(|pi| { - // Print backtrace if RUST_BACKTRACE=1 + std::panic::set_hook(Box::new(|info| { let bt = Backtrace::capture(); if bt.status() == BacktraceStatus::Captured { eprintln!("Panic backtrace:\n{}", bt); } else { - eprintln!("Panicked, backtrace not captured: {:?}", bt.status()); + eprintln!("Panic message:\n{}", info.to_string()); } report_diagnostic(Diagnostic { message: format!( "Compiler bug during {} ('{}'), please report at https://www.winglang.io/contributing/start-here/bugs", CompilationContext::get_phase(), - pi, + info, ), span: Some(CompilationContext::get_span()), annotations: vec![], diff --git a/libs/wingc/src/generate_docs.rs b/libs/wingc/src/generate_docs.rs new file mode 100644 index 00000000000..68e286a8b6b --- /dev/null +++ b/libs/wingc/src/generate_docs.rs @@ -0,0 +1,494 @@ +use camino::{Utf8Path, Utf8PathBuf}; +use indexmap::IndexMap; +use wingii::{fqn::FQN, type_system::TypeSystem}; + +use crate::{ + ast::{AccessModifier, Phase, Scope}, + closure_transform::ClosureTransformer, + diagnostic::{found_errors, report_diagnostic, Diagnostic, DiagnosticSeverity}, + emit_warning_for_unsupported_package_managers, + file_graph::{File, FileGraph}, + files::Files, + find_nearest_wing_project_dir, + fold::Fold, + jsify::JSifier, + lifting::LiftVisitor, + parser::{as_wing_library, normalize_path, parse_wing_project}, + type_check::{ + type_reference_transform::TypeReferenceTransformer, ClassLike, FunctionSignature, HasFqn, Namespace, NamespaceRef, + SymbolEnvOrNamespace, SymbolKind, Type, TypeRef, Types, UnsafeRef, VariableKind, + }, + type_check_assert::TypeCheckAssert, + type_check_file, + valid_json_visitor::ValidJsonVisitor, + visit::Visit, +}; + +/// Generate documentation for the project +pub fn generate_docs(project_dir: &Utf8Path) -> Result { + let project_dir = find_nearest_wing_project_dir(project_dir); + let source_package = as_wing_library(&project_dir, false); + if source_package.is_none() { + report_diagnostic(Diagnostic { + message: "No package.json found in project directory".to_string(), + span: None, + annotations: vec![], + hints: vec![], + severity: DiagnosticSeverity::Error, + }); + return Err(()); + } + let source_package = source_package.unwrap(); + let source_path = normalize_path(&project_dir, None); + let source_file = File::new(&source_path, source_package.clone()); + + // A map from package names to their root directories + let mut library_roots: IndexMap = IndexMap::new(); + library_roots.insert(source_package.clone(), project_dir.to_owned()); + + // -- PARSING PHASE -- + let mut files = Files::new(); + let mut file_graph = FileGraph::default(); + let mut tree_sitter_trees = IndexMap::new(); + let mut asts = IndexMap::new(); + let topo_sorted_files = parse_wing_project( + &source_file, + None, + &mut files, + &mut file_graph, + &mut library_roots, + &mut tree_sitter_trees, + &mut asts, + ); + + emit_warning_for_unsupported_package_managers(&project_dir); + + // -- DESUGARING PHASE -- + + // Transform all inflight closures defined in preflight into single-method resources + let mut asts = asts + .into_iter() + .map(|(path, scope)| { + let mut inflight_transformer = ClosureTransformer::new(); + let scope = inflight_transformer.fold_scope(scope); + (path, scope) + }) + .collect::>(); + + // -- TYPECHECKING PHASE -- + + // Create universal types collection (need to keep this alive during entire compilation) + let mut types = Types::new(); + let mut jsii_types = TypeSystem::new(); + + // Create a universal JSII import spec (need to keep this alive during entire compilation) + let mut jsii_imports = vec![]; + + // Type check all files in topological order (start with files that don't bring any other + // Wing files, then move on to files that depend on those, and repeat) + for file in &topo_sorted_files { + let mut scope = asts.swap_remove(&file.path).expect("matching AST not found"); + type_check_file( + &mut scope, + &mut types, + &file, + &file_graph, + &library_roots, + &mut jsii_types, + &mut jsii_imports, + ); + + // Make sure all type reference are no longer considered references + let mut tr_transformer = TypeReferenceTransformer { types: &mut types }; + let scope = tr_transformer.fold_scope(scope); + + // Validate the type checker didn't miss anything - see `TypeCheckAssert` for details + let mut tc_assert = TypeCheckAssert::new(&types, found_errors()); + tc_assert.check(&scope); + + // Validate all Json literals to make sure their values are legal + let mut json_checker = ValidJsonVisitor::new(&types); + json_checker.check(&scope); + + asts.insert(file.path.to_owned(), scope); + } + + let jsifier = JSifier::new( + &mut types, + &files, + &file_graph, + &source_path, + // out_dir will not be used + &source_path, + ); + + // -- LIFTING PHASE -- + + for (_path, scope) in &asts { + let mut lift = LiftVisitor::new(&jsifier); + lift.visit_scope(&scope); + } + + // bail out now (before jsification) if there are errors (no point in jsifying) + if found_errors() { + return Err(()); + } + + // -- DOC GENERATION PHASE -- + return generate_docs_helper(&types, &source_path); +} + +const HIDDEN_METHODS: [&str; 6] = [ + "toString", + "toJSON", + "onLift", + "onLiftType", + "toInflight", + "isConstruct", +]; + +fn generate_docs_helper(types: &Types, project_dir: &Utf8Path) -> Result { + let mut docs = String::new(); + let root_env = types.source_file_envs.get(project_dir).expect("No root env found"); + let ns = match root_env { + SymbolEnvOrNamespace::Namespace(ns) => *ns, + SymbolEnvOrNamespace::SymbolEnv(_) => panic!("Root env is not a namespace"), + SymbolEnvOrNamespace::Error(diag) => panic!("Error in root env: {}", diag), + }; + + docs.push_str("

API Reference

\n\n"); + + let mut public_types = vec![]; + let namespaces = find_documentable_namespaces_recursive(&ns); + for ns in namespaces { + public_types.extend(find_public_types_in_namespace(&ns)); + } + + print_table_of_contents(&public_types, &mut docs); + print_classes(&public_types, &mut docs); + print_interfaces(&public_types, &mut docs); + print_structs(&public_types, &mut docs); + print_enums(&public_types, &mut docs); + + Ok(docs) +} + +/// Return a list of all of the public namespaces directly in this namespace and all of its children, including +/// this namespace if it has any public elements. +fn find_documentable_namespaces_recursive(ns: &Namespace) -> Vec { + let mut namespaces = vec![]; + if ns.has_public_api_elements() { + namespaces.push(UnsafeRef::from(ns)); + } + for env in ns.envs.iter() { + for entry in env.symbol_map.values() { + if let SymbolKind::Namespace(child_ns) = entry.kind { + if entry.access == AccessModifier::Public { + namespaces.extend(find_documentable_namespaces_recursive(&child_ns)); + } + } + } + } + namespaces +} + +/// Return a list of all public types directly in this namespace (does not include types in child namespaces). +fn find_public_types_in_namespace(ns: &Namespace) -> Vec { + let mut entries = vec![]; + for env in ns.envs.iter() { + for (_name, entry) in env.symbol_map.iter() { + if entry.access == AccessModifier::Public { + if let SymbolKind::Type(typ) = entry.kind { + entries.push(typ); + } + } + } + } + return entries; +} + +fn simplified_fqn(typ: &TypeRef) -> String { + let fqn = typ.fqn().expect("Type has no FQN"); + let fqn = FQN::from(fqn.as_str()); + fqn + .as_str() + .strip_prefix(fqn.assembly()) + .expect("FQN assembly prefix not found") + .strip_prefix('.') + .expect("FQN dot not found") + .to_string() +} + +fn print_table_of_contents(types: &[TypeRef], docs: &mut String) { + docs.push_str("

Table of Contents

\n\n"); + + let mut classes = vec![]; + let mut interfaces = vec![]; + let mut structs = vec![]; + let mut enums = vec![]; + + for typ in types { + match **typ { + Type::Class(_) => classes.push(typ), + Type::Interface(_) => interfaces.push(typ), + Type::Struct(_) => structs.push(typ), + Type::Enum(_) => enums.push(typ), + _ => panic!("Unexpected type in public types list"), + } + } + + if !classes.is_empty() { + docs.push_str("- **Classes**\n"); + for class in &classes { + docs.push_str(" - "); + docs.push_str(&simplified_fqn(class)); + docs.push_str("\n"); + } + } + + if !interfaces.is_empty() { + docs.push_str("- **Interfaces**\n"); + for interface in &interfaces { + docs.push_str(" - "); + docs.push_str(&simplified_fqn(interface)); + docs.push_str("\n"); + } + } + + if !structs.is_empty() { + docs.push_str("- **Structs**\n"); + for struct_ in &structs { + docs.push_str(" - "); + docs.push_str(&simplified_fqn(struct_)); + docs.push_str("\n"); + } + } + + if !enums.is_empty() { + docs.push_str("- **Enums**\n"); + for enum_ in &enums { + docs.push_str(" - "); + docs.push_str(&simplified_fqn(enum_)); + docs.push_str("\n"); + } + } + + docs.push_str("\n"); +} + +fn print_classes(types: &[TypeRef], docs: &mut String) { + for typ in types { + if let Type::Class(ref class) = **typ { + docs.push_str("

"); + docs.push_str(&simplified_fqn(typ)); + docs.push_str(" ("); + docs.push_str(&class.phase.to_string()); + docs.push_str(" class)

\n\n"); + + let docstring = class.docs.render(); + if !docstring.is_empty() { + docs.push_str(&docstring); + docs.push_str("\n\n"); + } + + print_constructors(docs, class); + print_properties(docs, class); + print_methods(docs, class); + } + } +} + +fn print_interfaces(types: &[TypeRef], docs: &mut String) { + for typ in types { + if let Type::Interface(ref interface) = **typ { + docs.push_str("

"); + docs.push_str(&simplified_fqn(typ)); + docs.push_str(" (interface)

\n\n"); + + let docstring = interface.docs.render(); + if !docstring.is_empty() { + docs.push_str(&docstring); + docs.push_str("\n\n"); + } + + print_properties(docs, interface); + print_methods(docs, interface); + } + } +} + +fn print_structs(types: &[TypeRef], docs: &mut String) { + for typ in types { + if let Type::Struct(ref struct_) = **typ { + docs.push_str("

"); + docs.push_str(&simplified_fqn(typ)); + docs.push_str(" (struct)

\n\n"); + + let docstring = struct_.docs.render(); + if !docstring.is_empty() { + docs.push_str(&docstring); + docs.push_str("\n\n"); + } + + print_properties(docs, struct_); + } + } +} + +fn print_enums(types: &[TypeRef], docs: &mut String) { + for typ in types { + if let Type::Enum(ref enum_) = **typ { + docs.push_str("

"); + docs.push_str(&simplified_fqn(typ)); + docs.push_str(" (enum)

\n\n"); + + let docstring = enum_.docs.render(); + if !docstring.is_empty() { + docs.push_str(&docstring); + docs.push_str("\n\n"); + } + + docs.push_str("

Values

\n\n"); + docs.push_str("| **Name** | **Description** |\n"); + docs.push_str("| --- | --- |\n"); + for (name, description) in enum_.values.iter() { + docs.push_str("| "); + docs.push_str(&name.name); + docs.push_str(" | "); + if let Some(description) = description { + docs.push_str(&description); + } else { + docs.push_str("*No description*"); + } + docs.push_str(" |\n"); + } + + docs.push_str("\n"); + } + } +} + +fn print_constructors(docs: &mut String, class: &impl ClassLike) { + docs.push_str("

Constructor

\n\n"); + + let mut constructors = class.constructors(true).collect::>(); + constructors.retain(|(_, constructor_info)| constructor_info.access == AccessModifier::Public); + // We only care about printing the preflight constructor + constructors.retain(|(_, constructor_info)| constructor_info.phase == Phase::Preflight); + + if constructors.is_empty() { + docs.push_str("*No constructor*\n"); + } else { + docs.push_str("
\n");
+		for (_, constructor_info) in constructors {
+			let sig = constructor_info
+				.type_
+				.as_function_sig()
+				.expect("Constructor is not a function");
+			print_signature(&VariableKind::InstanceMember, "new", sig, docs);
+			docs.push_str("\n");
+		}
+		docs.push_str("
\n"); + } + + docs.push_str("\n"); +} + +fn print_properties(docs: &mut String, class: &impl ClassLike) { + docs.push_str("

Properties

\n\n"); + + let mut fields = class.fields(true).collect::>(); + fields.retain(|(_, field_info)| field_info.access == AccessModifier::Public); + + if fields.is_empty() { + docs.push_str("*No properties*\n"); + } else { + docs.push_str("| **Name** | **Type** | **Description** |\n"); + docs.push_str("| --- | --- | --- |\n"); + for (name, prop_info) in fields { + docs.push_str("| "); + docs.push_str(&name); + docs.push_str(" | "); + docs.push_str(&prop_info.type_.to_string()); + docs.push_str(" | "); + let prop_summary = prop_info.docs.and_then(|d| d.summary); + if let Some(prop_summary) = prop_summary { + docs.push_str(&prop_summary.replace('\n', " ")); + } else { + docs.push_str("*No description*"); + } + docs.push_str(" |\n"); + } + } + + docs.push_str("\n"); +} + +fn print_methods(docs: &mut String, class: &impl ClassLike) { + docs.push_str("

Methods

\n\n"); + + let mut methods = class.methods(true).collect::>(); + methods.retain(|(_, method_info)| method_info.access == AccessModifier::Public); + methods.retain(|(name, _)| !HIDDEN_METHODS.contains(&name.as_str())); + + if methods.is_empty() { + docs.push_str("*No methods*\n"); + } else { + docs.push_str("| **Signature** | **Description** |\n"); + docs.push_str("| --- | --- |\n"); + for (name, method_info) in methods { + let sig = method_info.type_.as_function_sig().expect("Method is not a function"); + + docs.push_str("| "); + print_signature(&method_info.kind, &name, sig, docs); + docs.push_str(" | "); + let method_summary = method_info.docs.and_then(|d| d.summary); + if let Some(method_summary) = method_summary { + docs.push_str(&method_summary.replace('\n', " ")); + } else { + docs.push_str("*No description*"); + } + docs.push_str(" |\n"); + } + } + + docs.push_str("\n"); +} + +fn print_signature(var_kind: &VariableKind, name: &str, sig: &FunctionSignature, docs: &mut String) { + if var_kind == &VariableKind::StaticMember { + docs.push_str("static "); + } + if sig.phase != Phase::Preflight { + docs.push_str(&sig.phase.to_string()); + docs.push_str(" "); + } + docs.push_str(name); + docs.push_str("("); + for (i, param) in sig.parameters.iter().enumerate() { + if i > 0 { + docs.push_str(", "); + } + docs.push_str(¶m.name); + docs.push_str(": "); + docs.push_str(¶m.typeref.to_string()); + } + docs.push_str("): "); + docs.push_str(&sig.return_type.to_string()); +} diff --git a/libs/wingc/src/lib.rs b/libs/wingc/src/lib.rs index d8d7c72578d..cfe6dfec75c 100644 --- a/libs/wingc/src/lib.rs +++ b/libs/wingc/src/lib.rs @@ -17,6 +17,7 @@ use dtsify::extern_dtsify::{is_extern_file, ExternDTSifier}; use file_graph::{File, FileGraph}; use files::Files; use fold::Fold; +use generate_docs::generate_docs; use indexmap::IndexMap; use jsify::JSifier; @@ -56,6 +57,7 @@ mod dtsify; mod file_graph; mod files; pub mod fold; +pub mod generate_docs; pub mod jsify; pub mod json_schema_generator; mod lifting; @@ -164,26 +166,6 @@ pub unsafe extern "C" fn wingc_malloc(size: usize) -> *mut u8 { } } -const LOCKFILES: [&'static str; 4] = ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]; - -/// Wing sometimes can't find dependencies if they're installed with pnpm/yarn/bun. -/// Try to anticipate any issues that may arise from using pnpm/yarn/bun with winglibs -/// by emitting a warning if dependencies were installed with any of these package managers. -fn emit_warning_for_unsupported_package_managers(project_dir: &Utf8Path) { - for lockfile in &LOCKFILES { - let lockfile_path = project_dir.join(lockfile); - if lockfile_path.exists() { - report_diagnostic(Diagnostic { - message: "The current project has a pnpm/yarn/bun lockfile. Wing hasn't been tested with package managers besides npm, so it may be unable to resolve dependencies to Wing libraries when using these tools. See https://github.com/winglang/wing/issues/6129 for more details.".to_string(), - span: None, - annotations: vec![], - hints: vec![], - severity: DiagnosticSeverity::Warning, - }); - } - } -} - /// Expose a deallocation function to the WASM host /// /// _This implementation is copied from wasm-bindgen_ @@ -245,12 +227,45 @@ pub unsafe extern "C" fn wingc_compile(ptr: u32, len: u32) -> u64 { } } -pub fn type_check( +#[no_mangle] +pub unsafe extern "C" fn wingc_generate_docs(ptr: u32, len: u32) -> u64 { + let args = ptr_to_str(ptr, len); + let project_dir = Utf8Path::new(args); + let results = generate_docs(project_dir); + + if let Ok(results) = results { + string_to_combined_ptr(results) + } else { + WASM_RETURN_ERROR + } +} + +const LOCKFILES: [&'static str; 4] = ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]; + +/// Wing sometimes can't find dependencies if they're installed with pnpm/yarn/bun. +/// Try to anticipate any issues that may arise from using pnpm/yarn/bun with winglibs +/// by emitting a warning if dependencies were installed with any of these package managers. +fn emit_warning_for_unsupported_package_managers(project_dir: &Utf8Path) { + for lockfile in &LOCKFILES { + let lockfile_path = project_dir.join(lockfile); + if lockfile_path.exists() { + report_diagnostic(Diagnostic { + message: "The current project has a pnpm/yarn/bun lockfile. Wing hasn't been tested with package managers besides npm, so it may be unable to resolve dependencies to Wing libraries when using these tools. See https://github.com/winglang/wing/issues/6129 for more details.".to_string(), + span: None, + annotations: vec![], + hints: vec![], + severity: DiagnosticSeverity::Warning, + }); + } + } +} + +pub fn type_check_file( scope: &mut Scope, types: &mut Types, file: &File, file_graph: &FileGraph, - library_roots: &mut IndexMap, + library_roots: &IndexMap, jsii_types: &mut TypeSystem, jsii_imports: &mut Vec, ) { @@ -312,7 +327,7 @@ pub fn find_nearest_wing_project_dir(source_path: &Utf8Path) -> Utf8PathBuf { pub fn compile(source_path: &Utf8Path, source_text: Option, out_dir: &Utf8Path) -> Result { let project_dir = find_nearest_wing_project_dir(source_path); - let source_package = as_wing_library(&project_dir).unwrap_or_else(|| DEFAULT_PACKAGE_NAME.to_string()); + let source_package = as_wing_library(&project_dir, false).unwrap_or_else(|| DEFAULT_PACKAGE_NAME.to_string()); let source_path = normalize_path(source_path, None); let source_file = File::new(&source_path, source_package.clone()); @@ -362,7 +377,7 @@ pub fn compile(source_path: &Utf8Path, source_text: Option, out_dir: &Ut // Wing files, then move on to files that depend on those, and repeat) for file in &topo_sorted_files { let mut scope = asts.swap_remove(&file.path).expect("matching AST not found"); - type_check( + type_check_file( &mut scope, &mut types, &file, diff --git a/libs/wingc/src/lsp/snapshots/hovers/class_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/class_doc.snap index fd05488a179..2c2fbcfbfe5 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/class_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/class_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nclass Foo {\n // No public members\n}\n```\n---\n Class doc" + value: "```wing\nclass Foo {\n // No public members\n}\n```\n---\nClass doc" range: start: line: 2 diff --git a/libs/wingc/src/lsp/snapshots/hovers/class_doc_with_multiline_and_markdown.snap b/libs/wingc/src/lsp/snapshots/hovers/class_doc_with_multiline_and_markdown.snap index 241f80a4a26..c8ebd842478 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/class_doc_with_multiline_and_markdown.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/class_doc_with_multiline_and_markdown.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nclass Foo {\n a: num;\n}\n```\n---\n Class doc\n With multiline\n ## And markdown" + value: "```wing\nclass Foo {\n a: num;\n}\n```\n---\nClass doc\nWith multiline\n## And markdown" range: start: line: 4 diff --git a/libs/wingc/src/lsp/snapshots/hovers/class_field_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/class_field_doc.snap index 99064f04659..640eec9dc02 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/class_field_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/class_field_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\npreflight field: num\n```\n---\n Class field doc" + value: "```wing\npreflight field: num\n```\n---\nClass field doc" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/class_method_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/class_method_doc.snap index b05ad8ecf6f..26f4f60c155 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/class_method_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/class_method_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\npreflight do(): void\n```\n---\n Class method doc" + value: "```wing\npreflight do(): void\n```\n---\nClass method doc" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/ctor_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/ctor_doc.snap index d1b08ecdbaa..23ed3ba37ba 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/ctor_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/ctor_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nnew(): Bob\n```\n---\n I'm bob the constructor" + value: "```wing\nnew(): Bob\n```\n---\nI'm bob the constructor" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/ctor_doc_from_new_expr.snap b/libs/wingc/src/lsp/snapshots/hovers/ctor_doc_from_new_expr.snap index 45fc03e5ef3..a12841d7721 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/ctor_doc_from_new_expr.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/ctor_doc_from_new_expr.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nnew(): Bob\n```\n---\n I'm bob the constructor" + value: "```wing\nnew(): Bob\n```\n---\nI'm bob the constructor" range: start: line: 5 diff --git a/libs/wingc/src/lsp/snapshots/hovers/enum_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/enum_doc.snap index 276411098e7..c4e8d16c16f 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/enum_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/enum_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nenum Foo {\n}\n```\n---\n Enum doc" + value: "```wing\nenum Foo {\n}\n```\n---\nEnum doc" range: start: line: 2 diff --git a/libs/wingc/src/lsp/snapshots/hovers/ignoe_empty_lines_in_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/ignoe_empty_lines_in_doc.snap index 8c94a552d96..8affdebc8dd 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/ignoe_empty_lines_in_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/ignoe_empty_lines_in_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nclass Foo {\n // No public members\n}\n```\n---\n Class doc with empty lines after it" + value: "```wing\nclass Foo {\n // No public members\n}\n```\n---\nClass doc with empty lines after it" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc.snap index 9408e1280a1..819dcc54273 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\ninflight new(): Bob\n```\n---\n I'm bob the constructor (inflight)" + value: "```wing\ninflight new(): Bob\n```\n---\nI'm bob the constructor (inflight)" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc_from_new_expr.snap b/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc_from_new_expr.snap index 95c2f6f7070..90df94219c4 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc_from_new_expr.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/inflight_ctor_doc_from_new_expr.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\ninflight new(): Bob\n```\n---\n I'm bob the constructor (inflight)" + value: "```wing\ninflight new(): Bob\n```\n---\nI'm bob the constructor (inflight)" range: start: line: 5 diff --git a/libs/wingc/src/lsp/snapshots/hovers/inherited_interface_method_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/inherited_interface_method_doc.snap index f6a194f22c5..23421f8620c 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/inherited_interface_method_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/inherited_interface_method_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\npreflight do(): void\n```\n---\n Parent interface method doc" + value: "```wing\npreflight do(): void\n```\n---\nParent interface method doc" range: start: line: 9 diff --git a/libs/wingc/src/lsp/snapshots/hovers/inherited_struct_field_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/inherited_struct_field_doc.snap index 37d05837728..e9cae3d1665 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/inherited_struct_field_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/inherited_struct_field_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nfield: num\n```\n---\n Parent struct field doc" + value: "```wing\nfield: num\n```\n---\nParent struct field doc" range: start: line: 9 diff --git a/libs/wingc/src/lsp/snapshots/hovers/interface_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/interface_doc.snap index d085a5e4263..8c0e88cbec2 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/interface_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/interface_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\ninterface Foo extends IResource {\n // No public members\n}\n```\n---\n Interface doc" + value: "```wing\ninterface Foo extends IResource {\n // No public members\n}\n```\n---\nInterface doc" range: start: line: 2 diff --git a/libs/wingc/src/lsp/snapshots/hovers/interface_method_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/interface_method_doc.snap index 9e25195b051..488b8dac8cd 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/interface_method_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/interface_method_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\npreflight do(): void\n```\n---\n Interface method doc" + value: "```wing\npreflight do(): void\n```\n---\nInterface method doc" range: start: line: 3 diff --git a/libs/wingc/src/lsp/snapshots/hovers/member_doc_on_same_line_as_something_else.snap b/libs/wingc/src/lsp/snapshots/hovers/member_doc_on_same_line_as_something_else.snap index 46da7552606..e5b47b57a72 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/member_doc_on_same_line_as_something_else.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/member_doc_on_same_line_as_something_else.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\npreflight field: num\n```\n---\n Member doc in unexpected place" + value: "```wing\npreflight field: num\n```\n---\nMember doc in unexpected place" range: start: line: 2 diff --git a/libs/wingc/src/lsp/snapshots/hovers/struct_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/struct_doc.snap index a3c19a15423..06c6c9ab1d4 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/struct_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/struct_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nstruct Foo {\n // No public members\n}\n```\n---\n Struct doc" + value: "```wing\nstruct Foo {\n // No public members\n}\n```\n---\nStruct doc" range: start: line: 2 diff --git a/libs/wingc/src/lsp/snapshots/hovers/struct_field_doc.snap b/libs/wingc/src/lsp/snapshots/hovers/struct_field_doc.snap index affa401135b..2091e4c3e6c 100644 --- a/libs/wingc/src/lsp/snapshots/hovers/struct_field_doc.snap +++ b/libs/wingc/src/lsp/snapshots/hovers/struct_field_doc.snap @@ -3,7 +3,7 @@ source: libs/wingc/src/lsp/hover.rs --- contents: kind: markdown - value: "```wing\nfield: num\n```\n---\n Struct field doc" + value: "```wing\nfield: num\n```\n---\nStruct field doc" range: start: line: 3 diff --git a/libs/wingc/src/lsp/sync.rs b/libs/wingc/src/lsp/sync.rs index 9586567a3bf..7c23a67adae 100644 --- a/libs/wingc/src/lsp/sync.rs +++ b/libs/wingc/src/lsp/sync.rs @@ -22,7 +22,7 @@ use crate::valid_json_visitor::ValidJsonVisitor; use crate::visit::Visit; use crate::wasm_util::extern_json_fn; use crate::{ast::Scope, type_check::Types}; -use crate::{find_nearest_wing_project_dir, type_check, DEFAULT_PACKAGE_NAME}; +use crate::{find_nearest_wing_project_dir, type_check_file, DEFAULT_PACKAGE_NAME}; /// The output of compiling a Wing project with one or more files pub struct ProjectData { @@ -207,7 +207,7 @@ fn partial_compile( .asts .swap_remove(&file.path) .expect("matching AST not found"); - type_check( + type_check_file( &mut scope, &mut types, &file, diff --git a/libs/wingc/src/parser.rs b/libs/wingc/src/parser.rs index 9a73f7d39b7..91c0c6f9f51 100644 --- a/libs/wingc/src/parser.rs +++ b/libs/wingc/src/parser.rs @@ -1139,7 +1139,7 @@ impl<'s> Parser<'s> { .insert(module_name_parsed.clone(), module_dir.clone()); // If the package.json has a `wing` field, then we treat it as a Wing library - if let Some(libname) = as_wing_library(&Utf8Path::new(&module_dir)) { + if let Some(libname) = as_wing_library(&Utf8Path::new(&module_dir), true) { return if let Some(alias) = alias { // make sure the Wing library is also parsed let module_file = File::new(&module_dir, &libname); @@ -2920,6 +2920,7 @@ struct DocBuilder<'a> { parser: &'a Parser<'a>, } +#[derive(Debug)] enum DocBuilderResult { Done(Option), Continue, @@ -2936,12 +2937,15 @@ impl<'a> DocBuilder<'a> { fn process_node(&mut self, node: &Node) -> DocBuilderResult { if node.kind() == "doc" { - self.doc_lines.push( - self - .parser - .get_child_field(&node, "content") - .map_or("", |n| self.parser.node_text(&n)), - ); + let line = self + .parser + .get_child_field(&node, "content") + .map_or("", |n| self.parser.node_text(&n)); + if line.len() > 0 && line.chars().next() == Some(' ') { + self.doc_lines.push(&line[1..]); + } else { + self.doc_lines.push(line); + } DocBuilderResult::Continue } else if node.is_extra() { DocBuilderResult::Skip @@ -2985,7 +2989,7 @@ fn get_actual_children_by_field_name<'a>(node: Node<'a>, field_name: &str) -> Ve /// Check if the package.json in the given directory has a `wing` field. /// If so, return the name of the library. -pub fn as_wing_library(module_dir: &Utf8Path) -> Option { +pub fn as_wing_library(module_dir: &Utf8Path, require_wing_field: bool) -> Option { let package_json_path = Utf8Path::new(module_dir).join("package.json"); if !package_json_path.exists() { return None; @@ -3003,7 +3007,11 @@ pub fn as_wing_library(module_dir: &Utf8Path) -> Option { match package_json.get("wing") { Some(_) => {} - None => return None, + None => { + if require_wing_field { + return None; + } + } } match package_json.get("name") { diff --git a/libs/wingc/src/type_check.rs b/libs/wingc/src/type_check.rs index a4cd7000953..e839e766160 100644 --- a/libs/wingc/src/type_check.rs +++ b/libs/wingc/src/type_check.rs @@ -20,6 +20,7 @@ use crate::comp_ctx::{CompilationContext, CompilationPhase}; use crate::diagnostic::{report_diagnostic, Diagnostic, DiagnosticAnnotation, DiagnosticSeverity, TypeError, WingSpan}; use crate::docs::Docs; use crate::file_graph::{File, FileGraph}; +use crate::parser::normalize_path; use crate::type_check::has_type_stmt::HasStatementVisitor; use crate::type_check::symbol_env::SymbolEnvKind; use crate::visit::Visit; @@ -309,6 +310,14 @@ pub struct Namespace { pub fqn: String, } +impl Namespace { + /// Returns true if this namespace has any public API elements, like a `pub` class or enum, + /// or a namepsace with 1 or more public elements. + pub fn has_public_api_elements(&self) -> bool { + self.envs.iter().any(|env| env.has_public_api_elements()) + } +} + #[derive(Debug)] pub enum ResolveSource { /// A wing file within the source tree for this compilation. @@ -406,7 +415,11 @@ pub trait ClassLike: Display { fn methods(&self, with_ancestry: bool) -> ClassLikeIterator<'_> { self.get_env().iter(with_ancestry).filter_map(|(s, sym_kind, ..)| { if sym_kind.as_variable()?.type_.as_function_sig().is_some() { - Some((s, sym_kind.as_variable()?.clone())) + if s == CLASS_INIT_NAME || s == CLASS_INFLIGHT_INIT_NAME { + None + } else { + Some((s, sym_kind.as_variable()?.clone())) + } } else { None } @@ -423,6 +436,16 @@ pub trait ClassLike: Display { }) } + fn constructors(&self, with_ancestry: bool) -> ClassLikeIterator<'_> { + self.get_env().iter(with_ancestry).filter_map(|(s, sym_kind, ..)| { + if s == CLASS_INIT_NAME || s == CLASS_INFLIGHT_INIT_NAME { + Some((s, sym_kind.as_variable()?.clone())) + } else { + None + } + }) + } + fn get_method(&self, name: &Symbol) -> Option<&VariableInfo> { let v = self.get_env().lookup_ext(name, None).ok()?.0.as_variable()?; if v.type_.is_closure() { @@ -501,6 +524,53 @@ pub struct EnumInstance { pub enum_value: Symbol, } +pub trait HasFqn { + /// Obtain the fully qualified name of the symbol + fn fqn(&self) -> Option; +} + +impl HasFqn for Class { + fn fqn(&self) -> Option { + self.fqn.clone() + } +} + +impl HasFqn for Interface { + fn fqn(&self) -> Option { + Some(self.fqn.clone()) + } +} + +impl HasFqn for Struct { + fn fqn(&self) -> Option { + Some(self.fqn.clone()) + } +} + +impl HasFqn for Enum { + fn fqn(&self) -> Option { + Some(self.fqn.clone()) + } +} + +impl HasFqn for Namespace { + fn fqn(&self) -> Option { + Some(self.fqn.clone()) + } +} + +impl HasFqn for Type { + fn fqn(&self) -> Option { + match self { + Type::Class(c) => c.fqn(), + Type::Interface(i) => i.fqn(), + Type::Struct(s) => s.fqn(), + Type::Enum(e) => e.fqn(), + _ => None, + } + } +} + trait Subtype { /// Returns true if `self` is a subtype of `other`. /// @@ -3696,7 +3766,12 @@ It should primarily be used in preflight or in inflights that are guaranteed to } let ns = self.types.add_namespace(Namespace { - name: self.source_file.path.file_stem().unwrap().to_string(), + name: self + .source_file + .path + .file_stem() + .unwrap_or_else(|| DEFAULT_PACKAGE_NAME) + .to_string(), envs: child_envs, source_package: self.source_file.package.clone(), module_path: ResolveSource::WingFile, @@ -5436,7 +5511,7 @@ It should primarily be used in preflight or in inflights that are guaranteed to let parent_initializer = parent_class .as_class() .unwrap() - .methods(false) + .constructors(false) .find(|(name, ..)| name == parent_init_name) .unwrap() .1; @@ -7357,21 +7432,31 @@ fn lookup_known_type(name: &'static str, env: &SymbolEnv) -> TypeRef { /// /// let fqn3 = calculate_fqn_for_namespace("@winglibs/dynamodb", "/foo/bar".into(), "/foo/bar/".into()); /// assert_eq!(fqn3, "@winglibs/dynamodb".to_string()); +/// +/// let fqn4 = calculate_fqn_for_namespace("@winglibs/dynamodb", ".".into(), "impl.w".into()); +/// assert_eq!(fqn4, "@winglibs/dynamodb".to_string()); +/// +/// let fqn5 = calculate_fqn_for_namespace("@winglibs/dynamodb", ".".into(), "foo/impl.w".into()); +/// assert_eq!(fqn5, "@winglibs/dynamodb.foo".to_string()); /// ``` pub fn calculate_fqn_for_namespace(package_name: &str, package_root: &Utf8Path, path: &Utf8Path) -> String { - let assembly = package_name; - if !path.starts_with(package_root) { + let normalized_root = normalize_path(&package_root, None); + let normalized = normalize_path(&path, None); + if normalized.starts_with("..") { panic!( "File path \"{}\" is not within the package root \"{}\"", path, package_root ); } - let path = if path.as_str().ends_with(".w") { - path.parent().expect("Expected a parent directory") + let assembly = package_name; + let normalized = if normalized.as_str().ends_with(".w") { + normalized.parent().expect("Expected a parent directory") } else { - path + &normalized }; - let relative_path = path.strip_prefix(package_root).expect("not a prefix"); + let relative_path = normalized + .strip_prefix(&normalized_root) + .expect(format!("not a prefix: {} {}", normalized_root, normalized).as_str()); if relative_path == Utf8Path::new("") { return assembly.to_string(); } diff --git a/libs/wingc/src/type_check/symbol_env.rs b/libs/wingc/src/type_check/symbol_env.rs index 9d3bdfca0a5..89529683cff 100644 --- a/libs/wingc/src/type_check/symbol_env.rs +++ b/libs/wingc/src/type_check/symbol_env.rs @@ -517,6 +517,13 @@ impl SymbolEnv { pub fn is_in_function(&self) -> bool { matches!(self.kind, SymbolEnvKind::Function { .. }) } + + pub fn has_public_api_elements(&self) -> bool { + self + .symbol_map + .iter() + .any(|(_, entry)| entry.access == AccessModifier::Public) + } } pub struct SymbolEnvIter<'a> { diff --git a/libs/wingcompiler/src/compile.ts b/libs/wingcompiler/src/compile.ts index 71ec4f208c2..319b4e9d865 100644 --- a/libs/wingcompiler/src/compile.ts +++ b/libs/wingcompiler/src/compile.ts @@ -148,8 +148,8 @@ export async function compile(entrypoint: string, options: CompileOptions): Prom const targetdir = options.targetDir ?? join(dirname(entrypoint), "target"); const entrypointFile = resolve(entrypoint); log?.("wing file: %s", entrypointFile); - const wingDir = resolve(dirname(entrypointFile)); - log?.("wing dir: %s", wingDir); + const projectDir = resolve(dirname(entrypointFile)); + log?.("wing dir: %s", projectDir); const testing = options.testing ?? false; log?.("testing: %s", testing); const target = determineTargetFromPlatforms(options.platform); @@ -176,7 +176,7 @@ export async function compile(entrypoint: string, options: CompileOptions): Prom return nodeModules; }; - let wingNodeModules = nearestNodeModules(wingDir); + let wingNodeModules = nearestNodeModules(projectDir); if (!existsSync(synthDir)) { await fs.mkdir(workDir, { recursive: true }); @@ -184,7 +184,7 @@ export async function compile(entrypoint: string, options: CompileOptions): Prom const compileForPreflightResult = await compileForPreflight({ entrypointFile, workDir, - wingDir, + projectDir, synthDir, color: options.color, log, @@ -202,7 +202,7 @@ export async function compile(entrypoint: string, options: CompileOptions): Prom WING_TARGET: target, WING_PLATFORMS: resolvePlatformPaths(options.platform), WING_SYNTH_DIR: synthDir, - WING_SOURCE_DIR: wingDir, + WING_SOURCE_DIR: projectDir, WING_IS_TEST: process.env["WING_IS_TEST"] ?? testing.toString(), WING_VALUES: options.value?.length == 0 ? undefined : options.value, WING_VALUES_FILE: options.values ?? defaultValuesFile(), @@ -266,88 +266,110 @@ interface CompileForPreflightResult { async function compileForPreflight(props: { entrypointFile: string; workDir: string; - wingDir: string; + projectDir: string; synthDir: string; color?: boolean; log?: (...args: any[]) => void; }): Promise { if (props.entrypointFile.endsWith(".ts")) { - const diagnostics: wingCompiler.WingDiagnostic[] = []; - let typescriptFramework; - try { - typescriptFramework = (await import("@wingcloud/framework")).internal; - } catch (err) { - return { - preflightEntrypoint: "", - diagnostics: [ - { - message: `\ + return await compileTypeScriptForPreflight(props); + } else { + return await compileWingForPreflight(props); + } +} + +async function compileTypeScriptForPreflight(props: { + entrypointFile: string; + workDir: string; + projectDir: string; + synthDir: string; + color?: boolean; + log?: (...args: any[]) => void; +}): Promise { + const diagnostics: wingCompiler.WingDiagnostic[] = []; + let typescriptFramework; + try { + typescriptFramework = (await import("@wingcloud/framework")).internal; + } catch (err) { + return { + preflightEntrypoint: "", + diagnostics: [ + { + message: `\ Failed to load "@wingcloud/framework": ${(err as any).message} To use Wing with TypeScript files, you must install "@wingcloud/framework" as a dependency of your project. npm i @wingcloud/framework `, - severity: "error", - annotations: [], - hints: [], - }, - ], - }; - } - - return { - preflightEntrypoint: await typescriptFramework.compile({ - workDir: props.workDir, - entrypoint: props.entrypointFile, - }), - diagnostics, - }; - } else { - let env: Record = { - RUST_BACKTRACE: "full", - WING_SYNTH_DIR: normalPath(props.synthDir), + severity: "error", + annotations: [], + hints: [], + }, + ], }; - if (props.color !== undefined) { - env.CLICOLOR = props.color ? "1" : "0"; - } + } - const wingc = await wingCompiler.load({ - env, - imports: { - env: { - send_diagnostic, - }, - }, - }); + return { + preflightEntrypoint: await typescriptFramework.compile({ + workDir: props.workDir, + entrypoint: props.entrypointFile, + }), + diagnostics, + }; +} - const diagnostics: wingCompiler.WingDiagnostic[] = []; +async function compileWingForPreflight(props: { + entrypointFile: string; + workDir: string; + projectDir: string; + synthDir: string; + color?: boolean; + log?: (...args: any[]) => void; +}) { + let env: Record = { + RUST_BACKTRACE: "full", + WING_SYNTH_DIR: normalPath(props.synthDir), + }; + if (props.color !== undefined) { + env.CLICOLOR = props.color ? "1" : "0"; + } - function send_diagnostic(data_ptr: number, data_len: number) { - const data_buf = Buffer.from( - (wingc.exports.memory as WebAssembly.Memory).buffer, - data_ptr, - data_len - ); - const data_str = new TextDecoder().decode(data_buf); - diagnostics.push(JSON.parse(data_str)); - } + const wingc = await wingCompiler.load({ + env, + imports: { + env: { + send_diagnostic, + }, + }, + }); - const arg = `${normalPath(props.entrypointFile)};${normalPath(props.workDir)}`; - props.log?.(`invoking %s with: "%s"`, WINGC_COMPILE, arg); - let compilerOutput: string | number = ""; - try { - compilerOutput = wingCompiler.invoke(wingc, WINGC_COMPILE, arg); - } catch (error) { - // This is a bug in the compiler, indicate a compilation failure. - // The bug details should be part of the diagnostics handling below. - } + const diagnostics: wingCompiler.WingDiagnostic[] = []; - return { - preflightEntrypoint: join(props.workDir, WINGC_PREFLIGHT), - compilerOutput: JSON.parse(compilerOutput as string), - diagnostics, - }; + function send_diagnostic(data_ptr: number, data_len: number) { + const data_buf = Buffer.from( + (wingc.exports.memory as WebAssembly.Memory).buffer, + data_ptr, + data_len + ); + const data_str = new TextDecoder().decode(data_buf); + diagnostics.push(JSON.parse(data_str)); } + + const arg = `${normalPath(props.entrypointFile)};${normalPath(props.workDir)}`; + props.log?.(`invoking %s with: "%s"`, WINGC_COMPILE, arg); + let compilerOutput: string | number = ""; + try { + compilerOutput = wingCompiler.invoke(wingc, WINGC_COMPILE, arg); + } catch (error) { + // This is a bug in the compiler, indicate a compilation failure. + // The bug details should be part of the diagnostics handling below. + } + + return { + preflightEntrypoint: join(props.workDir, WINGC_PREFLIGHT), + compilerOutput: JSON.parse(compilerOutput as string), + diagnostics, + }; } /** diff --git a/libs/wingcompiler/src/generateDocs.ts b/libs/wingcompiler/src/generateDocs.ts new file mode 100644 index 00000000000..90e4ca0688b --- /dev/null +++ b/libs/wingcompiler/src/generateDocs.ts @@ -0,0 +1,59 @@ +import { normalPath } from "./util"; +import * as wingCompiler from "./wingc"; + +const WINGC_GENERATE_DOCS = "wingc_generate_docs"; + +export interface GenerateWingDocsOutput { + docsContents: string; + diagnostics: wingCompiler.WingDiagnostic[]; +} + +export async function generateWingDocs(props: { + projectDir: string; + color?: boolean; + log?: (...args: any[]) => void; +}): Promise { + let env: Record = { + RUST_BACKTRACE: "full", + }; + if (props.color !== undefined) { + env.CLICOLOR = props.color ? "1" : "0"; + } + + const wingc = await wingCompiler.load({ + env, + imports: { + env: { + send_diagnostic, + }, + }, + }); + + const diagnostics: wingCompiler.WingDiagnostic[] = []; + + function send_diagnostic(data_ptr: number, data_len: number) { + const data_buf = Buffer.from( + (wingc.exports.memory as WebAssembly.Memory).buffer, + data_ptr, + data_len + ); + const data_str = new TextDecoder().decode(data_buf); + diagnostics.push(JSON.parse(data_str)); + } + + const arg = normalPath(props.projectDir); + props.log?.(`invoking %s with: "%s"`, WINGC_GENERATE_DOCS, arg); + let docsContents: string = ""; + const result = wingCompiler.invoke(wingc, WINGC_GENERATE_DOCS, arg); + if (typeof result === "number") { + // This is a bug in the compiler, indicate a compilation failure. + // The bug details should be part of the diagnostics handling below. + } else { + docsContents = result; + } + + return { + docsContents, + diagnostics, + }; +} diff --git a/libs/wingcompiler/src/index.ts b/libs/wingcompiler/src/index.ts index 2f91af57046..25de2d9b79b 100644 --- a/libs/wingcompiler/src/index.ts +++ b/libs/wingcompiler/src/index.ts @@ -1,4 +1,5 @@ -export * from "./errors"; export * from "./compile"; export * from "./constants"; +export * from "./errors"; +export * from "./generateDocs"; export * from "./wingc"; diff --git a/libs/wingcompiler/src/wingc.ts b/libs/wingcompiler/src/wingc.ts index 0d270d4ad48..ce0b09b86b1 100644 --- a/libs/wingcompiler/src/wingc.ts +++ b/libs/wingcompiler/src/wingc.ts @@ -7,6 +7,7 @@ import { promisify } from "util"; export type WingCompilerFunction = | "wingc_compile" + | "wingc_generate_docs" | "wingc_on_did_open_text_document" | "wingc_on_did_change_text_document" | "wingc_on_completion" diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md index 9ea26b0f30e..92cbe8e63ce 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_extend_non_entry.test.w_compile_tf-aws.md @@ -50,7 +50,7 @@ class $Root extends $stdlib.std.Resource { let $preflightTypesMap = {}; const lib = $helpers.bringJs(`${__dirname}/preflight.extendnonentrypoint-1.cjs`, $preflightTypesMap); $helpers.nodeof(this).root.$preflightTypesMap = $preflightTypesMap; - const f = globalThis.$ClassFactory.new("rootpkg.Foo", lib.Foo, this, "Foo"); + const f = globalThis.$ClassFactory.new("examples-valid.Foo", lib.Foo, this, "Foo"); } } const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "bring_extend_non_entry.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] }); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md index 957cdfea1f9..e04703627b4 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_local.test.w_compile_tf-aws.md @@ -351,7 +351,7 @@ class $Root extends $stdlib.std.Resource { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure2-3.cjs")({ $expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"))}, - $file2_Q: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"))}, + $file2_Q: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir.Q") ?? file2.Q, "", "Q"))}, }) `; } @@ -359,11 +359,11 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), ["equal"]], - [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"), ["greet"]], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir.Q") ?? file2.Q, "", "Q"), ["greet"]], ], "$inflight_init": [ [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("@winglang/sdk.expect.Util") ?? expect.Util, "@winglang/sdk/expect", "Util"), []], - [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir.Q") ?? file2.Q, "", "Q"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir.Q") ?? file2.Q, "", "Q"), []], ], }); } @@ -405,8 +405,8 @@ class $Root extends $stdlib.std.Resource { }); } } - const store = globalThis.$ClassFactory.new("rootpkg.Store", file1.Store, this, "Store"); - const q = globalThis.$ClassFactory.new("rootpkg.subdir.Q", file2.Q, this, "Q"); + const store = globalThis.$ClassFactory.new("examples-valid.Store", file1.Store, this, "Store"); + const q = globalThis.$ClassFactory.new("examples-valid.subdir.Q", file2.Q, this, "Q"); (expect.Util.equal((file2.Q.preflightGreet("foo")), "Hello foo")); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:add data to store", new $Closure1(this, "$Closure1")); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:greet", new $Closure2(this, "$Closure2")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md index d5e8329578d..257ddadf939 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/bring_local_dir.test.w_compile_tf-aws.md @@ -106,13 +106,13 @@ class $Root extends $stdlib.std.Resource { const w = $helpers.bringJs(`${__dirname}/preflight.widget-1.cjs`, $preflightTypesMap); const subdir = $helpers.bringJs(`${__dirname}/preflight.subdir2-6.cjs`, $preflightTypesMap); $helpers.nodeof(this).root.$preflightTypesMap = $preflightTypesMap; - const widget1 = globalThis.$ClassFactory.new("rootpkg.subdir2.inner.Widget", w.Widget, this, "widget1"); + const widget1 = globalThis.$ClassFactory.new("examples-valid.subdir2.inner.Widget", w.Widget, this, "widget1"); $helpers.assert($helpers.eq((widget1.compute()), 42), "widget1.compute() == 42"); - const foo = globalThis.$ClassFactory.new("rootpkg.subdir2.Foo", subdir.Foo, this, "Foo"); + const foo = globalThis.$ClassFactory.new("examples-valid.subdir2.Foo", subdir.Foo, this, "Foo"); $helpers.assert($helpers.eq((foo.foo()), "foo"), "foo.foo() == \"foo\""); - const bar = globalThis.$ClassFactory.new("rootpkg.subdir2.Bar", subdir.Bar, this, "Bar"); + const bar = globalThis.$ClassFactory.new("examples-valid.subdir2.Bar", subdir.Bar, this, "Bar"); $helpers.assert($helpers.eq((bar.bar()), "bar"), "bar.bar() == \"bar\""); - const widget2 = globalThis.$ClassFactory.new("rootpkg.subdir2.inner.Widget", subdir.inner.Widget, this, "widget2"); + const widget2 = globalThis.$ClassFactory.new("examples-valid.subdir2.inner.Widget", subdir.inner.Widget, this, "widget2"); $helpers.assert($helpers.eq((widget2.compute()), 42), "widget2.compute() == 42"); $helpers.assert($helpers.eq((foo.checkWidget(widget2)), 1379), "foo.checkWidget(widget2) == 1379"); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md index 439b2583e82..30ebbce8b88 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_class_capture_preflight_object.test.w_compile_tf-aws.md @@ -447,7 +447,7 @@ class $Root extends $stdlib.std.Resource { static _toInflightType() { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure6-5.cjs")({ - $subdir_InflightClass: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"))}, + $subdir_InflightClass: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"))}, }) `; } @@ -455,11 +455,11 @@ class $Root extends $stdlib.std.Resource { return ({ "handle": [ [$helpers.preflightClassSingleton(this, 5), ["method"]], - [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], ], "$inflight_init": [ [$helpers.preflightClassSingleton(this, 5), []], - [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("rootpkg.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], + [$stdlib.core.toLiftableModuleType(globalThis.$ClassFactory.resolveType("examples-valid.subdir2.InflightClass") ?? subdir.InflightClass, "", "InflightClass"), []], ], }); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md index 309599ab482..72473383f1f 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/lift_shared_resource.test.w_compile_tf-aws.md @@ -407,8 +407,8 @@ class $Root extends $stdlib.std.Resource { } } const bucket = globalThis.$ClassFactory.new("@winglang/sdk.cloud.Bucket", cloud.Bucket, this, "Bucket"); - const b1 = globalThis.$ClassFactory.new("rootpkg.MyBucket", MyBucket, this, "b1", bucket); - const b2 = globalThis.$ClassFactory.new("rootpkg.MyBucket", MyBucket, this, "b2", bucket); + const b1 = globalThis.$ClassFactory.new("examples-valid.MyBucket", MyBucket, this, "b1", bucket); + const b2 = globalThis.$ClassFactory.new("examples-valid.MyBucket", MyBucket, this, "b2", bucket); const api = globalThis.$ClassFactory.new("@winglang/sdk.cloud.Api", cloud.Api, this, "Api"); (api.get("/", new $Closure1(this, "$Closure1"))); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:call endpoint", new $Closure2(this, "$Closure2")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md index ae2ca35ab4a..ec59b89ce79 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static.test.w_compile_tf-aws.md @@ -338,7 +338,7 @@ class LibClass extends $stdlib.std.Resource { super($scope, $id); } static createFoo($scope, id) { - return globalThis.$ClassFactory.new("rootpkg.Foo", Foo, $scope, id); + return globalThis.$ClassFactory.new("examples-valid.Foo", Foo, $scope, id); } static _toInflightType() { return ` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md index caf8656b25e..d444e077fa7 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/new_in_static_lib.w_compile_tf-aws.md @@ -57,7 +57,7 @@ class LibClass extends $stdlib.std.Resource { super($scope, $id); } static createFoo($scope, id) { - return globalThis.$ClassFactory.new("rootpkg.Foo", Foo, $scope, id); + return globalThis.$ClassFactory.new("examples-valid.Foo", Foo, $scope, id); } static _toInflightType() { return ` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md index b9db451237c..6b9b3f87a39 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/struct_from_json.test.w_compile_tf-aws.md @@ -485,7 +485,7 @@ class $Root extends $stdlib.std.Resource { (std.Number.fromJson("cool", { unsafe: true })); $macros.__Struct_fromJson(false, Student, ({"obviously": "not a student"}), { unsafe: true }); globalThis.$ClassFactory.new("@winglang/sdk.std.Test", std.Test, this, "test:unsafe flight", new $Closure5(this, "$Closure5")); - globalThis.$ClassFactory.new("rootpkg.subdir.UsesStructInImportedFile", otherExternalStructs.UsesStructInImportedFile, this, "UsesStructInImportedFile"); + globalThis.$ClassFactory.new("examples-valid.subdir.UsesStructInImportedFile", otherExternalStructs.UsesStructInImportedFile, this, "UsesStructInImportedFile"); } } const $APP = $PlatformManager.createApp({ outdir: $outdir, name: "struct_from_json.test", rootConstruct: $Root, isTestEnvironment: $wing_is_test, entrypointDir: process.env['WING_SOURCE_DIR'], rootId: process.env['WING_ROOT_ID'] });