Skip to content

Commit a37ca26

Browse files
authored
Merge pull request #57 from fossa-app/fix-f-nullable-ts-props
Preserve F# nullable reference props in generated TypeScript
2 parents d6dfc94 + 9f0fb6d commit a37ca26

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

.build.ps1

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,123 @@ param(
3131

3232
Set-StrictMode -Version Latest
3333

34+
function Update-FableNullableReferenceTypes {
35+
param(
36+
[Parameter(Mandatory = $true)]
37+
[string]
38+
$ProjectPath,
39+
[Parameter(Mandatory = $true)]
40+
[string]
41+
$FableOutputPath
42+
)
43+
44+
$projectFolder = Split-Path -Path $ProjectPath -Parent
45+
[xml]$projectXml = Get-Content -Path $ProjectPath -Raw
46+
$nullableFieldsByType = @{}
47+
48+
foreach ($compileItem in $projectXml.SelectNodes('//Compile')) {
49+
$sourcePath = Join-Path -Path $projectFolder -ChildPath $compileItem.Include
50+
if (-not (Test-Path -Path $sourcePath)) {
51+
continue
52+
}
53+
54+
$currentType = $null
55+
56+
foreach ($line in Get-Content -Path $sourcePath) {
57+
if ($line -match '^\s*type\s+([A-Za-z_][A-Za-z0-9_''`]*)') {
58+
$currentType = $Matches[1] -replace '`.*$', ''
59+
}
60+
61+
if ($currentType -and ($line -match '^\s*\{?\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?!Nullable<)(.+?)\s*\|\s*null\b')) {
62+
if (-not $nullableFieldsByType.ContainsKey($currentType)) {
63+
$nullableFieldsByType[$currentType] = [System.Collections.Generic.HashSet[string]]::new()
64+
}
65+
66+
[void]$nullableFieldsByType[$currentType].Add($Matches[1])
67+
}
68+
}
69+
}
70+
71+
if ($nullableFieldsByType.Count -eq 0) {
72+
return
73+
}
74+
75+
foreach ($typescriptFile in Get-ChildItem -Path $FableOutputPath -Filter '*.ts' -Recurse -File) {
76+
$currentType = $null
77+
$fileChanged = $false
78+
$usesNullable = $false
79+
$usesNonNullValue = $false
80+
$updatedLines = foreach ($line in Get-Content -Path $typescriptFile.FullName) {
81+
$updatedLine = $line
82+
83+
if ($updatedLine -match '^\s*export\s+class\s+([A-Za-z_][A-Za-z0-9_$]*)\b') {
84+
$currentType = $Matches[1] -replace '\$.*$', ''
85+
}
86+
87+
if ($currentType -and $nullableFieldsByType.ContainsKey($currentType)) {
88+
foreach ($fieldName in $nullableFieldsByType[$currentType]) {
89+
$escapedFieldName = [regex]::Escape($fieldName)
90+
91+
$fieldPattern = "^(\s*readonly\s+$escapedFieldName\s*:\s*)(?!Nullable<)([^;]+)(;.*)$"
92+
if ($updatedLine -match $fieldPattern) {
93+
$updatedLine = [regex]::Replace($updatedLine, $fieldPattern, '$1Nullable<$2>$3')
94+
$fileChanged = $true
95+
$usesNullable = $true
96+
}
97+
98+
if ($updatedLine -match '^\s*constructor\(') {
99+
$parameterPattern = "(\b$escapedFieldName\s*:\s*)(?!Nullable<)([^,)]+)"
100+
$nextLine = [regex]::Replace($updatedLine, $parameterPattern, '$1Nullable<$2>')
101+
102+
if ($nextLine -ne $updatedLine) {
103+
$updatedLine = $nextLine
104+
$fileChanged = $true
105+
$usesNullable = $true
106+
}
107+
}
108+
}
109+
}
110+
111+
$nonNullValuePattern = 'UrlPart_op_Implicit_Z721C83C5\((?!nonNullValue\()([A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*)\)'
112+
$nextLine = [regex]::Replace($updatedLine, $nonNullValuePattern, 'UrlPart_op_Implicit_Z721C83C5(nonNullValue($1))')
113+
114+
if ($nextLine -ne $updatedLine) {
115+
$updatedLine = $nextLine
116+
$fileChanged = $true
117+
$usesNonNullValue = $true
118+
}
119+
120+
$updatedLine
121+
}
122+
123+
if ($fileChanged) {
124+
if ($usesNullable -and -not ($updatedLines -match 'import\s+\{[^}]*\bNullable\b[^}]*\}\s+from\s+"@fable-org/fable-library-ts/Util\.ts"')) {
125+
$updatedLines = foreach ($line in $updatedLines) {
126+
if ($line -match '^(import\s+\{)([^}]+)(\}\s+from\s+"@fable-org/fable-library-ts/Util\.ts";)$') {
127+
"$($Matches[1])$($Matches[2]), Nullable $($Matches[3])"
128+
}
129+
else {
130+
$line
131+
}
132+
}
133+
}
134+
135+
if ($usesNonNullValue -and -not ($updatedLines -match 'import\s+\{[^}]*\bnonNullValue\b[^}]*\}\s+from\s+"@fable-org/fable-library-ts/Option\.ts"')) {
136+
$updatedLines = foreach ($line in $updatedLines) {
137+
if ($line -match '^(import\s+\{)([^}]+)(\}\s+from\s+"@fable-org/fable-library-ts/Option\.ts";)$') {
138+
"$($Matches[1])$($Matches[2]), nonNullValue $($Matches[3])"
139+
}
140+
else {
141+
$line
142+
}
143+
}
144+
}
145+
146+
Set-Content -Path $typescriptFile.FullName -Value $updatedLines
147+
}
148+
}
149+
}
150+
34151
# Synopsis: Initialize folders and variables
35152
Task Init {
36153
$trashFolder = Join-Path -Path . -ChildPath '.trash'
@@ -238,6 +355,7 @@ Task PackNPM Build, Test, {
238355
$projectPath = Resolve-Path -Path 'src/Bridge/Bridge.fsproj'
239356

240357
Exec { dotnet fable $projectPath --outDir $fableOutputArtifactsFolder --language typescript --fableLib @fable-org/fable-library-ts }
358+
Update-FableNullableReferenceTypes -ProjectPath $projectPath -FableOutputPath $fableOutputArtifactsFolder
241359
Exec { npm version $nextVersion --no-git-tag-version --allow-same-version }
242360
Exec { npm install }
243361

0 commit comments

Comments
 (0)