Skip to content

Commit

Permalink
Merge pull request 1Password#173 from darrell-roberts/swift-decorator…
Browse files Browse the repository at this point in the history
…s-generics

Allow swift decorator constraints to apply to generics

Support CSharp

Create basic language implementation.
Start adding tests.

Add snapshot tests

Update slice type to map to IEnumerable instead of Array.
Drop support for Unit type and return an error.

Support anonymous structs

Update slice of user type test snapshot

Support namespace option

Support serialization with json attributes

Remove EnumLabel and use EnumMember attribute instead.
Update all test snapshots according to the new changes.

C# without naming convention option

Add required attribute

For newtonsoft json to require a non optional property, the property has
to have the required attribute. Otherwise, it allows it to be null and a
default value is provided.
  • Loading branch information
charlespierce authored and dandro committed Jun 30, 2024
2 parents 2262a56 + 0284e32 commit 77c3770
Show file tree
Hide file tree
Showing 28 changed files with 599 additions and 244 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Version 1.10.0-beta

## 1.10.0-beta.6

- Added support for skipping fields/variants via the `target_os` argument [#176](https://github.com/1Password/typeshare/pull/176)

## 1.10.0-beta.5

- Added support for Swift generic constraints via `#[typeshare(swiftGenericConstraints)]` [#174](https://github.com/1Password/typeshare/pull/174)
- Added Swift config option for defining constraints on `CodableVoid` generated type [#174](https://github.com/1Password/typeshare/pull/174)

## 1.10.0-beta.4

Fixed a bug involving `#[typeshare(skip)]` on fields in struct variants of enums.
Expand All @@ -14,7 +23,7 @@ This release brings support for multiple file generation, allowing splitting gen
files when used in large projects. This can dramatically increase compilation speed of
the generated files and increase maintainability.

This is a *pre-release* version which may have bugs or break compatibility.
This is a _pre-release_ version which may have bugs or break compatibility.

- Multiple file output [#166](https://github.com/1Password/typeshare/pull/166)

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "typeshare-cli"
version = "1.10.0-beta.4"
version = "1.10.0-beta.6"
edition = "2021"
description = "Command Line Tool for generating language files with typeshare"
license = "MIT OR Apache-2.0"
Expand All @@ -22,5 +22,5 @@ once_cell = "1"
rayon = "1.10"
serde = { version = "1", features = ["derive"] }
toml = "0.8"
typeshare-core = { path = "../core", version = "1.10.0-beta.4" }
typeshare-core = { path = "../core", version = "1.10.0-beta.6" }
anyhow = "1"
7 changes: 7 additions & 0 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub const ARG_GENERATE_CONFIG: &str = "generate-config-file";
pub const ARG_OUTPUT_FILE: &str = "output-file";
pub const ARG_OUTPUT_FOLDER: &str = "output-folder";
pub const ARG_FOLLOW_LINKS: &str = "follow-links";
pub const ARG_TARGET_OS: &str = "target_os";

#[cfg(feature = "go")]
const AVAILABLE_LANGUAGES: [&str; 5] = ["kotlin", "scala", "swift", "typescript", "go"];
Expand Down Expand Up @@ -147,5 +148,11 @@ pub(crate) fn build_command() -> Command<'static> {
.help("Directories within which to recursively find and process rust files")
.required_unless(ARG_GENERATE_CONFIG)
.min_values(1),
).arg(
Arg::new(ARG_TARGET_OS)
.long("target-os")
.help("Optional restrict to target_os")
.takes_value(true)
.required(false)
)
}
4 changes: 4 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub struct SwiftParams {
pub type_mappings: HashMap<String, String>,
pub default_decorators: Vec<String>,
pub default_generic_constraints: Vec<String>,
/// The contraints to apply to `CodableVoid`.
pub codablevoid_constraints: Vec<String>,
}

#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -62,6 +64,8 @@ pub(crate) struct Config {
pub scala: ScalaParams,
#[cfg(feature = "go")]
pub go: GoParams,
#[serde(skip)]
pub target_os: Option<String>,
}

pub(crate) fn store_config(config: &Config, file_path: Option<&str>) -> anyhow::Result<()> {
Expand Down
15 changes: 13 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use anyhow::{anyhow, Context};
use args::{
build_command, ARG_CONFIG_FILE_NAME, ARG_FOLLOW_LINKS, ARG_GENERATE_CONFIG, ARG_JAVA_PACKAGE,
ARG_KOTLIN_PREFIX, ARG_MODULE_NAME, ARG_OUTPUT_FOLDER, ARG_SCALA_MODULE_NAME,
ARG_SCALA_PACKAGE, ARG_SWIFT_PREFIX, ARG_TYPE,
ARG_SCALA_PACKAGE, ARG_SWIFT_PREFIX, ARG_TARGET_OS, ARG_TYPE,
};
use clap::ArgMatches;
use config::Config;
use ignore::{overrides::OverrideBuilder, types::TypesBuilder, WalkBuilder};
use parse::{all_types, parse_input, parser_inputs};
use rayon::iter::ParallelBridge;
use std::collections::HashMap;
#[cfg(feature = "go")]
use typeshare_core::language::Go;
Expand Down Expand Up @@ -103,17 +104,25 @@ fn main() -> anyhow::Result<()> {
}

let multi_file = options.value_of(ARG_OUTPUT_FOLDER).is_some();

let target_os = config.target_os.clone();

let lang = language(language_type, config, multi_file);
let ignored_types = lang.ignored_reference_types();

// The walker ignores directories that are git-ignored. If you need
// a git-ignored directory to be processed, add the specific directory to
// the list of directories given to typeshare when it's invoked in the
// makefiles
// TODO: The `ignore` walker supports parallel walking. We should use this
// and implement a `ParallelVisitor` that builds up the mapping of parsed
// data. That way both walking and parsing are in parallel.
// https://docs.rs/ignore/latest/ignore/struct.WalkParallel.html
let crate_parsed_data = parse_input(
parser_inputs(walker_builder, language_type, multi_file),
parser_inputs(walker_builder, language_type, multi_file).par_bridge(),
&ignored_types,
multi_file,
target_os,
)?;

// Collect all the types into a map of the file name they
Expand Down Expand Up @@ -146,6 +155,7 @@ fn language(
config.swift.default_generic_constraints,
),
multi_file,
codablevoid_constraints: config.swift.codablevoid_constraints,
..Default::default()
}),
SupportedLanguage::Kotlin => Box::new(Kotlin {
Expand Down Expand Up @@ -210,6 +220,7 @@ fn override_configuration(mut config: Config, options: &ArgMatches) -> Config {
config.go.package = go_package.to_string();
}

config.target_os = options.value_of(ARG_TARGET_OS).map(|s| s.to_string());
config
}

Expand Down
83 changes: 27 additions & 56 deletions cli/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use ignore::WalkBuilder;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::{
collections::{hash_map::Entry, HashMap},
ops::Not,
path::PathBuf,
};
use typeshare_core::{
Expand All @@ -28,12 +27,12 @@ pub fn parser_inputs(
walker_builder: WalkBuilder,
language_type: SupportedLanguage,
multi_file: bool,
) -> Vec<ParserInput> {
) -> impl Iterator<Item = ParserInput> {
walker_builder
.build()
.filter_map(Result::ok)
.filter(|dir_entry| !dir_entry.path().is_dir())
.filter_map(|dir_entry| {
.filter_map(move |dir_entry| {
let crate_name = if multi_file {
CrateName::find_crate_name(dir_entry.path())?
} else {
Expand All @@ -47,7 +46,6 @@ pub fn parser_inputs(
crate_name,
})
})
.collect()
}

/// The output file name to write to.
Expand Down Expand Up @@ -90,74 +88,47 @@ pub fn all_types(file_mappings: &HashMap<CrateName, ParsedData>) -> CrateTypes {

/// Collect all the parsed sources into a mapping of crate name to parsed data.
pub fn parse_input(
inputs: Vec<ParserInput>,
inputs: impl ParallelIterator<Item = ParserInput>,
ignored_types: &[&str],
multi_file: bool,
target_os: Option<String>,
) -> anyhow::Result<HashMap<CrateName, ParsedData>> {
inputs
.into_par_iter()
.try_fold(
HashMap::new,
|mut results: HashMap<CrateName, ParsedData>,
|mut parsed_crates: HashMap<CrateName, ParsedData>,
ParserInput {
file_path,
file_name,
crate_name,
}| {
match std::fs::read_to_string(&file_path)
.context("Failed to read input")
.and_then(|data| {
typeshare_core::parser::parse(
&data,
crate_name.clone(),
file_name.clone(),
file_path,
ignored_types,
multi_file,
)
.context("Failed to parse")
})
.map(|parsed_data| {
parsed_data.and_then(|parsed_data| {
is_parsed_data_empty(&parsed_data)
.not()
.then_some((crate_name, parsed_data))
})
})? {
Some((crate_name, parsed_data)) => {
match results.entry(crate_name) {
Entry::Occupied(mut entry) => {
entry.get_mut().add(parsed_data);
}
Entry::Vacant(entry) => {
entry.insert(parsed_data);
}
}
Ok::<_, anyhow::Error>(results)
}
None => Ok(results),
let parsed_result = typeshare_core::parser::parse(
&std::fs::read_to_string(&file_path)
.with_context(|| format!("Failed to read input: {file_name}"))?,
crate_name.clone(),
file_name.clone(),
file_path,
ignored_types,
multi_file,
target_os.clone(),
)
.with_context(|| format!("Failed to parse: {file_name}"))?;

if let Some(parsed_data) = parsed_result {
parsed_crates
.entry(crate_name)
.or_default()
.add(parsed_data);
}

Ok(parsed_crates)
},
)
.try_reduce(HashMap::new, |mut file_maps, mapping| {
for (crate_name, parsed_data) in mapping {
match file_maps.entry(crate_name) {
Entry::Occupied(mut e) => {
e.get_mut().add(parsed_data);
}
Entry::Vacant(e) => {
e.insert(parsed_data);
}
}
.try_reduce(HashMap::new, |mut file_maps, parsed_crates| {
for (crate_name, parsed_data) in parsed_crates {
file_maps.entry(crate_name).or_default().add(parsed_data);
}
Ok(file_maps)
})
}

/// Check if we have not parsed any relavent typehsared types.
fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool {
parsed_data.enums.is_empty()
&& parsed_data.aliases.is_empty()
&& parsed_data.structs.is_empty()
&& parsed_data.errors.is_empty()
}
5 changes: 3 additions & 2 deletions cli/src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,14 @@ fn check_write_file(outfile: &PathBuf, output: Vec<u8>) -> anyhow::Result<()> {
if !output.is_empty() {
let out_dir = outfile
.parent()
.context(format!("Could not get parent for {outfile:?}"))?;
.with_context(|| format!("Could not get parent for {outfile:?}"))?;
// If the output directory doesn't already exist, create it.
if !out_dir.exists() {
fs::create_dir_all(out_dir).context("failed to create output directory")?;
}

fs::write(outfile, output).context("failed to write output")?;
fs::write(outfile, output)
.with_context(|| format!("failed to write output: {}", outfile.to_string_lossy()))?;
}
Ok(())
}
Expand Down
3 changes: 2 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "typeshare-core"
version = "1.10.0-beta.4"
version = "1.10.0-beta.6"
license = "MIT OR Apache-2.0"
edition = "2021"
description = "The code generator used by Typeshare's command line tool"
Expand All @@ -20,3 +20,4 @@ anyhow = "1"
expect-test = "1.5"
once_cell = "1"
cool_asserts = "2"
syn = { version = "2", features = ["full", "visit", "extra-traits"] }
2 changes: 1 addition & 1 deletion core/data/tests/can_generate_generic_struct/output.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,4 @@ public enum CoreEnumUsingGenericStruct: Codable {
}

/// () isn't codable, so we use this instead to represent Rust's unit type
public struct CodableVoid: Codable {}
public struct CodableVoid: Codable, Equatable {}
2 changes: 1 addition & 1 deletion core/data/tests/can_handle_unit_type/output.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ public enum EnumHasVoidType: Codable {
}

/// () isn't codable, so we use this instead to represent Rust's unit type
public struct CodableVoid: Codable {}
public struct CodableVoid: Codable, Equatable {}
34 changes: 34 additions & 0 deletions core/data/tests/excluded_by_target_os/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![cfg(feature = "online")]
#![allow(dead_code)]

use std::collection::HashMap;

#[typeshare]
pub enum TestEnum {
Variant1,
#[cfg(target_os = "ios")]
Variant2,
#[cfg(any(target_os = "ios", feature = "test"))]
Variant3,
#[cfg(all(target_os = "ios", feature = "test"))]
Variant4,
#[cfg(target_os = "android")]
Variant5,
}

#[typeshare]
#[cfg(target_os = "ios")]
pub struct TestStruct;

#[typeshare]
#[cfg(target_os = "ios")]
type TypeAlias = String;

#[typeshare]
#[cfg(any(target_os = "ios", feature = "test"))]
pub enum Test {}

#[typeshare]
#[cfg(feature = "super")]
#[cfg(target_os = "android")]
pub enum SomeEnum {}
12 changes: 12 additions & 0 deletions core/data/tests/excluded_by_target_os/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package proto

import "encoding/json"

type TestEnum string
const (
TestEnumVariant1 TestEnum = "Variant1"
TestEnumVariant5 TestEnum = "Variant5"
)
type SomeEnum string
const (
)
17 changes: 17 additions & 0 deletions core/data/tests/excluded_by_target_os/output.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.agilebits.onepassword

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

@Serializable
enum class TestEnum(val string: String) {
@SerialName("Variant1")
Variant1("Variant1"),
@SerialName("Variant5")
Variant5("Variant5"),
}

@Serializable
enum class SomeEnum(val string: String) {
}

Loading

0 comments on commit 77c3770

Please sign in to comment.