Skip to content

Commit

Permalink
Add types_output (#163)
Browse files Browse the repository at this point in the history
* Add `types_output`

Co-authored-by: pashleyy <[email protected]>

* Update docs/config/options.md

Co-authored-by: Sasial <[email protected]>

* Set default

Co-authored-by: Sasial <[email protected]>

* Remove clone

Co-authored-by: Sasial <[email protected]>

* Fix formatting

* Fix formatting

* Fix formatting

* Add TypeScript output

* Remove formatting change

* Fix output

* Make type optional

* Add Selene lints

* Refactoring

* Lune tests

* Add newline to Selene tests

* Fix Lune tests

* Wrap tagged enums in parentheses

* Formatting

* Formatting

---------

Co-authored-by: pashleyy <[email protected]>
Co-authored-by: Sasial <[email protected]>
  • Loading branch information
3 people authored Feb 15, 2025
1 parent 2632e9e commit 608c066
Show file tree
Hide file tree
Showing 26 changed files with 319 additions and 5 deletions.
20 changes: 20 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ fn main() -> Result<()> {
std::fs::write(file_path, defs)?;
}

if let Some(types_output) = code.types {
let types_path = config_path.parent().unwrap().join(types_output.path);

if let Some(parent) = types_path.parent() {
std::fs::create_dir_all(parent)?;
}

if let Some(defs) = types_output.defs {
let defs_path = if types_path.file_stem().unwrap() == "init" {
types_path.with_file_name("index.d.ts")
} else {
types_path.with_extension("d.ts")
};

std::fs::write(defs_path, defs)?;
}

std::fs::write(types_path, types_output.code)?;
}

if let Some(tooling) = code.tooling {
let tooling_path = config_path.parent().unwrap().join(tooling.path);

Expand Down
12 changes: 12 additions & 0 deletions docs/config/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ opt client_output = "path/to/client/output.lua"`
const outputExample = `opt server_output = "./network/client.luau"
opt client_output = "src/client/zap.luau"`

const typesOutputExample = `opt types_output = "network/types.luau"`

const asyncLibExample = `opt yield_type = "promise"
opt async_lib = "require(game:GetService('ReplicatedStorage').Promise)"`

Expand Down Expand Up @@ -37,6 +39,16 @@ The paths are relative to the configuration file and should point to a lua(u) fi

<CodeBlock :code="outputExample" />

## `types_output` [`0.6.18+`]

Configures where Luau types will be output.

The path is relative to the configuration file and should point to a lua(u) file.

### Example

<CodeBlock :code="typesOutputExample" />

## `remote_scope`

This option changes the name of the remotes generated by Zap.
Expand Down
1 change: 1 addition & 0 deletions zap/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct Config<'src> {

pub server_output: &'src str,
pub client_output: &'src str,
pub types_output: Option<&'src str>,
pub tooling_output: &'src str,

pub casing: Casing,
Expand Down
6 changes: 6 additions & 0 deletions zap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct Output {
pub struct Code {
pub server: Output,
pub client: Output,
pub types: Option<Output>,
pub tooling: Option<Output>,
}

Expand Down Expand Up @@ -79,6 +80,11 @@ pub fn run(input: &str, no_warnings: bool) -> Return {
code: output::luau::client::code(&config),
defs: output::typescript::client::code(&config),
},
types: config.types_output.map(|types_output| Output {
path: types_output.into(),
code: output::luau::types::code(&config),
defs: output::typescript::types::code(&config),
}),
tooling: output::tooling::output(&config),
}),
diagnostics,
Expand Down
1 change: 1 addition & 0 deletions zap/src/output/luau/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{

pub mod client;
pub mod server;
pub mod types;

pub trait Output {
fn push(&mut self, s: &str);
Expand Down
71 changes: 71 additions & 0 deletions zap/src/output/luau/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::config::{Config, TyDecl};

use super::Output;

struct TypesOutput<'src> {
config: &'src Config<'src>,
tabs: u32,
buf: String,
}

impl Output for TypesOutput<'_> {
fn push(&mut self, s: &str) {
self.buf.push_str(s);
}

fn indent(&mut self) {
self.tabs += 1;
}

fn dedent(&mut self) {
self.tabs -= 1;
}

fn push_indent(&mut self) {
for _ in 0..self.tabs {
self.push("\t");
}
}
}

impl<'a> TypesOutput<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config,
tabs: 0,
buf: String::new(),
}
}

fn push_tydecl(&mut self, tydecl: &TyDecl) {
let name = &tydecl.name;
let ty = &tydecl.ty;

self.push_indent();
self.push(&format!("export type {name} = "));
self.push_ty(ty);
self.push("\n");
}

fn push_tydecls(&mut self) {
for tydecl in self.config.tydecls.iter() {
self.push_tydecl(tydecl);
}
}

pub fn output(mut self) -> String {
self.push_line(&format!(
"-- Types generated by Zap v{} (https://github.com/red-blox/zap)",
env!("CARGO_PKG_VERSION")
));

self.push_tydecls();
self.push_line("return nil");

self.buf
}
}

pub fn code(config: &Config) -> String {
TypesOutput::new(config).output()
}
1 change: 1 addition & 0 deletions zap/src/output/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::config::{Config, Enum, Parameter, Ty};

pub mod client;
pub mod server;
pub mod types;

pub trait ConfigProvider {
fn get_config(&self) -> &Config;
Expand Down
76 changes: 76 additions & 0 deletions zap/src/output/typescript/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::config::{Config, TyDecl};

use super::{ConfigProvider, Output};

struct TypesOutput<'src> {
config: &'src Config<'src>,
tabs: u32,
buf: String,
}

impl Output for TypesOutput<'_> {
fn push(&mut self, s: &str) {
self.buf.push_str(s);
}

fn indent(&mut self) {
self.tabs += 1;
}

fn dedent(&mut self) {
self.tabs -= 1;
}

fn push_indent(&mut self) {
for _ in 0..self.tabs {
self.push("\t");
}
}
}

impl ConfigProvider for TypesOutput<'_> {
fn get_config(&self) -> &Config {
self.config
}
}

impl<'a> TypesOutput<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config,
tabs: 0,
buf: String::new(),
}
}

fn push_tydecl(&mut self, tydecl: &TyDecl) {
let name = &tydecl.name;
let ty = &tydecl.ty;

self.push_indent();
self.push(&format!("export type {name} = "));
self.push_ty(ty);
self.push("\n");
}

fn push_tydecls(&mut self) {
for tydecl in self.config.tydecls.iter() {
self.push_tydecl(tydecl);
}
}

pub fn output(mut self) -> String {
self.push_line(&format!(
"// Types generated by Zap v{} (https://github.com/red-blox/zap)",
env!("CARGO_PKG_VERSION")
));

self.push_tydecls();

self.buf
}
}

pub fn code(config: &Config) -> Option<String> {
Some(TypesOutput::new(config).output())
}
17 changes: 17 additions & 0 deletions zap/src/parser/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ impl<'src> Converter<'src> {

let (server_output, ..) = self.str_opt("server_output", "network/server.lua", &config.opts);
let (client_output, ..) = self.str_opt("client_output", "network/client.lua", &config.opts);
let types_output: Option<&str> = self.types_output_opt(&config.opts);
let (tooling_output, ..) = self.str_opt("tooling_output", "network/tooling.lua", &config.opts);

let casing = self.casing_opt(&config.opts);
Expand All @@ -150,6 +151,7 @@ impl<'src> Converter<'src> {

server_output,
client_output,
types_output,
tooling_output,

casing,
Expand Down Expand Up @@ -233,6 +235,21 @@ impl<'src> Converter<'src> {
}
}

fn types_output_opt(&mut self, opts: &[SyntaxOpt<'src>]) -> Option<&'src str> {
let opt = opts.iter().find(|opt| opt.name.name == "types_output")?;

if let SyntaxOptValueKind::Str(opt_value) = &opt.value.kind {
Some(self.str(opt_value))
} else {
self.report(Report::AnalyzeInvalidOptValue {
span: opt.value.span(),
expected: "Types output path expected.",
});

None
}
}

fn boolean_opt(&mut self, name: &'static str, default: bool, opts: &[SyntaxOpt<'src>]) -> (bool, Option<Span>) {
let mut value = default;
let mut span = None;
Expand Down
8 changes: 7 additions & 1 deletion zap/tests/lune/base.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local luau = require("@lune/luau")
local process = require("@lune/process")
local roblox = require("@lune/roblox")
local task = require("@lune/task")
local serverCode, clientCode, toolingCode = unpack(process.args)
local serverCode, clientCode, typesCode, toolingCode = unpack(process.args)

local noop = function() end

Expand Down Expand Up @@ -80,6 +80,12 @@ local client = luau.load(clientCode, {
environment = environment
})()

local types = luau.load(typesCode, {
debugName = "Types",
codegenEnabled = true,
environment = environment,
})()

local tooling = luau.load(toolingCode, {
debugName = "Tooling",
codegenEnabled = true,
Expand Down
6 changes: 5 additions & 1 deletion zap/tests/lune/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use insta::{assert_debug_snapshot, glob, Settings};
use lune::Runtime;

pub fn run_lune_test(input: &str, no_warnings: bool, insta_settings: Settings) {
let script = zap::run(&format!("opt tooling = true\n{input}"), no_warnings);
let script = zap::run(
&format!("opt tooling = true\nopt types_output = \"network/types.luau\"\n{input}"),
no_warnings,
);

assert!(script.code.is_some(), "No code generated!");

let code = script.code.as_ref().unwrap();
let mut runtime = Runtime::new().with_args(vec![
&code.server.code,
&code.client.code,
&code.types.as_ref().unwrap().code,
&code.tooling.as_ref().unwrap().code,
]);

Expand Down
21 changes: 18 additions & 3 deletions zap/tests/selene/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ use selene_lib::{lints::Severity, CheckerDiagnostic};
static SELENE: LazyLock<Selene> = LazyLock::new(initialise_selene);

pub fn run_selene_test(input: &str, no_warnings: bool, insta_settings: &mut Settings, file_stem: Cow<'_, str>) {
let code = zap::run(&format!("opt tooling = true\n{input}"), no_warnings)
.code
.expect("Zap did not generate any code!");
let code = zap::run(
&format!("opt tooling = true\nopt types_output = \"network/types.luau\"\n{input}"),
no_warnings,
)
.code
.expect("Zap did not generate any code!");

let client_ast = full_moon::parse_fallible(&code.client.code, SELENE.lua_version).into_ast();
let client_diagnostics = SELENE
Expand Down Expand Up @@ -39,6 +42,18 @@ pub fn run_selene_test(input: &str, no_warnings: bool, insta_settings: &mut Sett
assert_debug_snapshot!(server_diagnostics);
});

let types_ast = full_moon::parse_fallible(&code.types.as_ref().unwrap().code, SELENE.lua_version).into_ast();
let types_diagnostics = SELENE
.linter
.test_on(&types_ast)
.into_iter()
.filter(|diagnostic| diagnostic.severity != Severity::Allow)
.collect::<Vec<CheckerDiagnostic>>();
insta_settings.set_snapshot_suffix(format!("{file_stem}@types"));
insta_settings.bind(|| {
assert_debug_snapshot!(types_diagnostics);
});

let tooling_ast = full_moon::parse_fallible(&code.tooling.as_ref().unwrap().code, SELENE.lua_version).into_ast();
let tooling_diagnostics = SELENE
.linter
Expand Down
6 changes: 6 additions & 0 deletions zap/tests/selene/snapshots/run_selene_test@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zap/tests/selene/mod.rs
expression: types_diagnostics
input_file: zap/tests/files/function.zap
---
[]
6 changes: 6 additions & 0 deletions zap/tests/selene/snapshots/run_selene_test@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zap/tests/selene/mod.rs
expression: types_diagnostics
input_file: zap/tests/files/function_mutiple_rets.zap
---
[]
6 changes: 6 additions & 0 deletions zap/tests/selene/snapshots/run_selene_test@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zap/tests/selene/mod.rs
expression: types_diagnostics
input_file: zap/tests/files/function_one_unnamed_parameter.zap
---
[]
6 changes: 6 additions & 0 deletions zap/tests/selene/snapshots/run_selene_test@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zap/tests/selene/mod.rs
expression: types_diagnostics
input_file: zap/tests/files/function_two_unnamed_parameters.zap
---
[]
6 changes: 6 additions & 0 deletions zap/tests/selene/snapshots/run_selene_test@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: zap/tests/selene/mod.rs
expression: types_diagnostics
input_file: zap/tests/files/many_assorted.zap
---
[]
Loading

0 comments on commit 608c066

Please sign in to comment.