Skip to content

Commit

Permalink
Merge pull request #3 from tomsugarev/TypeScript-generator
Browse files Browse the repository at this point in the history
TypeScript Standalone
  • Loading branch information
ollieatkinson authored Aug 12, 2022
2 parents a939924 + d7dc193 commit ad87202
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 1 deletion.
18 changes: 17 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let package = Package(
.library(name: "SwiftLexicon", targets: ["SwiftLexicon"]),
.library(name: "SwiftStandAlone", targets: ["SwiftStandAlone"]),
.library(name: "KotlinStandAlone", targets: ["KotlinStandAlone"]),
.library(name: "TypeScriptStandAlone", targets: ["TypeScriptStandAlone"]),
.library(name: "LexiconGenerators", targets: ["LexiconGenerators"]),
.executable(name: "lexicon-generate", targets: ["lexicon-generate"]),
.plugin(name: "SwiftStandAloneGeneratorPlugin", targets: ["SwiftStandAloneGeneratorPlugin"]),
Expand Down Expand Up @@ -45,7 +46,8 @@ let package = Package(
"Lexicon",
"SwiftLexicon",
"SwiftStandAlone",
"KotlinStandAlone"
"KotlinStandAlone",
"TypeScriptStandAlone"
]
),
.target(
Expand Down Expand Up @@ -90,6 +92,20 @@ let package = Package(
],
resources: [.copy("Resources")]
),
.target(
name: "TypeScriptStandAlone",
dependencies: [
"Lexicon",
]
),
.testTarget(
name: "TypeScriptStandAloneTests",
dependencies: [
"Hope",
"TypeScriptStandAlone"
],
resources: [.copy("Resources")]
),
.executableTarget(
name: "lexicon-generate",
dependencies: [
Expand Down
3 changes: 3 additions & 0 deletions Sources/LexiconGenerators/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Collections
import SwiftLexicon
import SwiftStandAlone
import KotlinStandAlone
import TypeScriptStandAlone

public extension Lexicon.Graph.JSON {

Expand All @@ -18,6 +19,8 @@ public extension Lexicon.Graph.JSON {

"Kotlin Stand-Alone": KotlinStandAlone.Generator.self,

"TypeScript Stand-Alone": TypeScriptStandAlone.Generator.self,

"JSON Classes & Mixins": JSONClasses.self,
]
}
137 changes: 137 additions & 0 deletions Sources/TypeScriptStandAlone/Generator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//
// github.com/screensailor 2022
//

import Lexicon
import UniformTypeIdentifiers

public extension UTType {
static var typescript = UTType(filenameExtension: "ts", conformingTo: .sourceCode)!
}

public enum Generator: CodeGenerator {

// TODO: prefixes?

public static let utType = UTType.typescript
public static let command = "ts"

public static func generate(_ json: Lexicon.Graph.JSON) throws -> Data {
return Data(json.ts().utf8)
}
}

private extension Lexicon.Graph.JSON {

func ts() -> String {
return """
interface I { }
// L
class L implements I {
protected id: string;
constructor(id: string) {
this.id = id;
}
get ['__']() {
return this.id;
}
}
// MARK: generated types
\(classes.flatMap{ $0.ts(prefix: ("L", "I"), classes: classes) }.joined(separator: "\n"))
const \(name) = new L_\(name)("\(name)");
"""
}
}

private extension Lexicon.Graph.Node.Class.JSON {

// TODO: make this more readable

func ts(prefix: (class: String, protocol: String), classes: [Lexicon.Graph.Node.Class.JSON]) -> [String] {

guard mixin == nil else {
return []
}

var lines: [String] = []
let T = id.idToClassSuffix
let (L, I) = prefix

if let protonym = protonym {
lines += "type \(L)_\(T) = \(L)_\(protonym.idToClassSuffix)"
return lines
}

lines += "class \(L)_\(T) extends \(L) implements \(I)_\(T) {"

let supertype = supertype?
.replacingOccurrences(of: "_", with: "__")
.replacingOccurrences(of: ".", with: "_")
.replacingOccurrences(of: "__&__", with: ", I_")

if hasNoProperties {
if supertype != nil {
let superChildren = classes.filter {$0.id == supertype}.first?.children

for child in superChildren ?? [] {
lines += " \(child)!: \(L)_\(supertype!)_\(child);"
}
lines += "}"

lines += "type \(I)_\(T) = I_\(supertype!);"
} else {
lines += "}"
lines += "type \(I)_\(T) = I;"
}
}

guard hasProperties else {
return lines
}

for t in type ?? [] {
let subClass = classes.filter{$0.id == t}.first
for child in subClass?.children ?? [] {
let id = "L.\(t).\(child)"
lines += " \(child)!: \(id.idToClassSuffix);"
}
if let keys = subClass?.synonyms?.keys {
for synonym in keys {
let id = "L.\(t).\(synonym)"
lines += " \(synonym)!: \(id.idToClassSuffix);"
}
}

}

for child in children ?? [] {
let id = "\(id).\(child)"
lines += " \(child) = new \(L)_\(id.idToClassSuffix)(`${this.__}.\(child)`);"
}

for (synonym, protonym) in (synonyms?.sortedByLocalizedStandard(by: \.key) ?? []) {
lines += " \(synonym) = this.\(protonym);"
}
lines += "}"

lines += "interface \(I)_\(T) extends \(I)\(supertype.map{ "_\($0)" } ?? "") {"

for child in children ?? [] {
let id = "\(id).\(child)"
lines += " \(child): \(I)_\(id.idToClassSuffix);"
}
lines += "}"
return lines
}
}

private extension String {
var idToClassSuffix: String {
replacingOccurrences(of: "_", with: "__")
.replacingOccurrences(of: ".", with: "_")
.replacingOccurrences(of: "_&_", with: "_")
}
}
17 changes: 17 additions & 0 deletions Tests/TypeScriptStandAloneTests/Resources/test.taskpaper
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
test:
one:
+ test.type.odd
more:
time:
+ test
two:
+ test.type.even
timing:
type:
even:
bad:
= no.good
no:
good:
odd:
good:
89 changes: 89 additions & 0 deletions Tests/TypeScriptStandAloneTests/Resources/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
interface I { }

// L
class L implements I {
protected id: string;
constructor(id: string) {
this.id = id;
}
get ['__']() {
return this.id;
}
}

// MARK: generated types
class L_test extends L implements I_test {
one = new L_test_one(`${this.__}.one`);
two = new L_test_two(`${this.__}.two`);
type = new L_test_type(`${this.__}.type`);
}
interface I_test extends I {
one: I_test_one;
two: I_test_two;
type: I_test_type;
}
class L_test_one extends L implements I_test_one {
good!: L_test_type_odd_good;
more = new L_test_one_more(`${this.__}.more`);
}
interface I_test_one extends I_test_type_odd {
more: I_test_one_more;
}
class L_test_one_more extends L implements I_test_one_more {
time = new L_test_one_more_time(`${this.__}.time`);
}
interface I_test_one_more extends I {
time: I_test_one_more_time;
}
class L_test_one_more_time extends L implements I_test_one_more_time {
one!: L_test_one;
two!: L_test_two;
type!: L_test_type;
}
type I_test_one_more_time = I_test;
class L_test_two extends L implements I_test_two {
no!: L_test_type_even_no;
bad!: L_test_type_even_bad;
timing = new L_test_two_timing(`${this.__}.timing`);
}
interface I_test_two extends I_test_type_even {
timing: I_test_two_timing;
}
class L_test_two_timing extends L implements I_test_two_timing {
}
type I_test_two_timing = I;
class L_test_type extends L implements I_test_type {
even = new L_test_type_even(`${this.__}.even`);
odd = new L_test_type_odd(`${this.__}.odd`);
}
interface I_test_type extends I {
even: I_test_type_even;
odd: I_test_type_odd;
}
class L_test_type_even extends L implements I_test_type_even {
no = new L_test_type_even_no(`${this.__}.no`);
bad = this.no.good;
}
interface I_test_type_even extends I {
no: I_test_type_even_no;
}
type L_test_type_even_bad = L_test_type_even_no_good
class L_test_type_even_no extends L implements I_test_type_even_no {
good = new L_test_type_even_no_good(`${this.__}.good`);
}
interface I_test_type_even_no extends I {
good: I_test_type_even_no_good;
}
class L_test_type_even_no_good extends L implements I_test_type_even_no_good {
}
type I_test_type_even_no_good = I;
class L_test_type_odd extends L implements I_test_type_odd {
good = new L_test_type_odd_good(`${this.__}.good`);
}
interface I_test_type_odd extends I {
good: I_test_type_odd_good;
}
class L_test_type_odd_good extends L implements I_test_type_odd_good {
}
type I_test_type_odd_good = I;
const test = new L_test("test");
28 changes: 28 additions & 0 deletions Tests/TypeScriptStandAloneTests/TypeScriptStandAlone™.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@_exported import Hope
@_exported import Combine
@_exported import Lexicon
@_exported import TypeScriptStandAlone

final class TypeScriptLexicon™: Hopes {

func test_generator() async throws {

var json = try await "test".taskpaper().lexicon().json()
json.date = Date(timeIntervalSinceReferenceDate: 0)

let code = try Generator.generate(json).string()

try hope(code) == "test.ts".file().string()
}

func test_code() throws {
/**
@Test
fun generator(){
assert(test.one.more.time.one.more.time.identifier == "test.one.more.time.one.more.time")
assert(test.two.bad == test.two.no.good)
assert(test.two.bad.identifier == "test.two.no.good")
}
*/
}
}
30 changes: 30 additions & 0 deletions Tests/TypeScriptStandAloneTests/util.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// github.com/screensailor 2022
//

import Foundation

extension String {

func taskpaper() throws -> String {
try "\(self).taskpaper".file().string()
}

func file() throws -> Data {
guard let url = Bundle.module.url(forResource: "Resources/\(self)", withExtension: nil) else {
throw "Could not find '\(self)'"
}
return try Data(contentsOf: url)
}

func lexicon() async throws -> Lexicon {
try await Lexicon.from(TaskPaper(self).decode())
}
}

extension Data {

func string(encoding: String.Encoding = .utf8) throws -> String {
try String(data: self, encoding: encoding).try()
}
}

0 comments on commit ad87202

Please sign in to comment.