Skip to content

Commit 6c3e17a

Browse files
authored
fix: new WorkflowScript("filename") special case and CGS023 parameter count validation (#110)
2 parents 3a215a0 + db5955a commit 6c3e17a

6 files changed

Lines changed: 95 additions & 9 deletions

File tree

Catglobe.CgScript.EditorSupport.Lsp.Tests/SemanticAnalyzerDiagnosticsTests.cs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,14 +528,14 @@ public void Constructor_WrongArgType_ReportsCGS023()
528528
}
529529

530530
[Fact]
531-
public void Constructor_NoArgs_NoCGS023()
531+
public void Constructor_NoArgs_ReportsCGS023()
532532
{
533-
// new String() — zero args is valid (all params treated as optional)
533+
// new String() — constructor requires a string arg; zero args is a mismatch
534534
var diags = AnalyzeWithObjects(
535535
"string s = new String();",
536536
new Dictionary<string, ObjectDefinition> { ["String"] = MakeStringDef() });
537537

538-
Assert.DoesNotContain(diags, d => d.Code == "CGS023");
538+
Assert.Contains(diags, d => d.Code == "CGS023" && d.Message.Contains("String"));
539539
}
540540

541541
[Fact]
@@ -569,6 +569,39 @@ public void KnownObjectsFromLoader_InvalidConstructorArgs_ReportsCGS023()
569569
Assert.Contains(diags, d => d.Code == "CGS023" && d.Message.Contains("String"));
570570
}
571571

572+
[Fact]
573+
public void WorkflowScript_FilenameOverload_NoCGS023()
574+
{
575+
// new WorkflowScript("filename") is a preprocessor special form that the source generator
576+
// replaces with new WorkflowScript(resourceId) on deployment. No CGS023 should be raised.
577+
// WithPreprocessorExtensions() must be called — this overload is NOT in the base definitions.
578+
var result = CgScriptParseService.Parse("WorkflowScript script = new WorkflowScript(\"myScript.cgs\");");
579+
var diags = SemanticAnalyzer.Analyze(result.Tree, new CgScriptDefinitions().WithPreprocessorExtensions());
580+
581+
Assert.DoesNotContain(diags, d => d.Code == "CGS023");
582+
}
583+
584+
[Fact]
585+
public void WorkflowScript_FilenameOverload_NotInjectedInBaseDefinitions()
586+
{
587+
// Without WithPreprocessorExtensions(), new WorkflowScript("filename") should report CGS023
588+
// because the base definitions only know about the real API constructors.
589+
var result = CgScriptParseService.Parse("WorkflowScript script = new WorkflowScript(\"myScript.cgs\");");
590+
var diags = SemanticAnalyzer.Analyze(result.Tree, new CgScriptDefinitions());
591+
592+
Assert.Contains(diags, d => d.Code == "CGS023");
593+
}
594+
595+
[Fact]
596+
public void WorkflowScript_TooManyStringArgs_ReportsCGS023()
597+
{
598+
// new WorkflowScript("a", "b", "c") — no constructor accepts three strings even with preprocessor extensions
599+
var result = CgScriptParseService.Parse("WorkflowScript script = new WorkflowScript(\"a\", \"b\", \"c\");");
600+
var diags = SemanticAnalyzer.Analyze(result.Tree, new CgScriptDefinitions().WithPreprocessorExtensions());
601+
602+
Assert.Contains(diags, d => d.Code == "CGS023" && d.Message.Contains("WorkflowScript"));
603+
}
604+
572605
// ── CGS024: method call argument mismatch ─────────────────────────────────
573606

574607
private static ObjectDefinition MakeStringWithCompareDef()
@@ -873,4 +906,25 @@ public void StringBuilderAppendFormat_WithMultipleArgs_NoCGS024()
873906

874907
Assert.DoesNotContain(diags, d => d.Code == "CGS024");
875908
}
909+
910+
[Fact]
911+
public void FormatFunction_WithMultipleArgs_NoCGS024()
912+
{
913+
// format("{0} {1}", a, b) — format accepts "Params object" so any number of args is valid.
914+
var result = CgScriptParseService.Parse("string s = format(\"{0} {1}\", 1, 2);");
915+
var diags = SemanticAnalyzer.Analyze(result.Tree, new CgScriptDefinitions());
916+
917+
Assert.DoesNotContain(diags, d => d.Code == "CGS024");
918+
}
919+
920+
[Fact]
921+
public void WorkflowScriptInvoke_WithArrayArg_NoCGS024()
922+
{
923+
// WorkflowScript.Invoke(array) — exactly one array argument is valid.
924+
// WithPreprocessorExtensions() is needed because new WorkflowScript("filename") is the preprocessor form.
925+
var result = CgScriptParseService.Parse("WorkflowScript ws = new WorkflowScript(\"test.cgs\"); array args = new array(); ws.Invoke(args);");
926+
var diags = SemanticAnalyzer.Analyze(result.Tree, new CgScriptDefinitions().WithPreprocessorExtensions());
927+
928+
Assert.DoesNotContain(diags, d => d.Code == "CGS024");
929+
}
876930
}

Catglobe.CgScript.EditorSupport.Lsp/CgScriptDefinitionsFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public static async Task<CgScriptDefinitions> CreateFromUrlAsync(string siteUrl,
3232
CgScriptDefinitions.TraceSource.TraceInformation(
3333
"Loaded definitions from {0}: {1} functions, {2} objects, {3} constants",
3434
url, result.Functions.Count, result.Objects.Count, result.Constants.Count);
35-
return result;
35+
return result.WithPreprocessorExtensions();
3636
}
3737
catch (JsonException ex) when (ex is not null)
3838
{
@@ -46,7 +46,7 @@ public static async Task<CgScriptDefinitions> CreateFromUrlAsync(string siteUrl,
4646
{
4747
CgScriptDefinitions.TraceSource.TraceEvent(TraceEventType.Warning, 0,
4848
"Failed to fetch definitions from {0} — falling back to bundled definitions: {1}", url, ex.Message);
49-
return new CgScriptDefinitions();
49+
return new CgScriptDefinitions().WithPreprocessorExtensions();
5050
}
5151
}
5252
}

Catglobe.CgScript.EditorSupport.Parsing/CgScriptDefinitions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,27 @@ public CgScriptDefinitions WithExtraGlobalVariables(IReadOnlyDictionary<string,
428428
Enums is Dictionary<string, EnumDefinition> ed ? ed : Enums.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase));
429429
}
430430

431+
/// <summary>
432+
/// Returns a new <see cref="CgScriptDefinitions"/> with extra constructor overloads that are
433+
/// only valid when editing CgScript files processed by the source generator or deployed through
434+
/// VS / VS Code (e.g., <c>new WorkflowScript("filename.cgs")</c> which the source generator
435+
/// rewrites to <c>new WorkflowScript(resourceId)</c>).
436+
/// Do NOT call this from the web runtime context where definitions come from a live server.
437+
/// </summary>
438+
public CgScriptDefinitions WithPreprocessorExtensions()
439+
{
440+
if (!ObjectMemberInfos.TryGetValue("WorkflowScript", out var wsMemberInfo)) return this;
441+
var modified = ObjectMemberInfos.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.Ordinal);
442+
modified["WorkflowScript"] = wsMemberInfo.WithExtraConstructorOverload(new[] { "string" });
443+
return new CgScriptDefinitions(
444+
Functions is Dictionary<string, FunctionDefinition> fd ? fd : Functions.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase),
445+
Objects is Dictionary<string, ObjectDefinition> od ? od : Objects.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase),
446+
Constants,
447+
GlobalVariables,
448+
Enums is Dictionary<string, EnumDefinition> ed ? ed : Enums.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase))
449+
{ ObjectMemberInfos = modified };
450+
}
451+
431452
/// <summary>
432453
/// Creates a <see cref="CgScriptDefinitions"/> by deserializing JSON from <paramref name="stream"/>.
433454
/// Used by the Lsp layer to load definitions fetched over HTTP.

Catglobe.CgScript.EditorSupport.Parsing/ObjectMemberInfo.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,16 @@ public ObjectMemberInfo(
7171

7272
/// <summary>Returns <c>true</c> when the type exposes a method with the given name.</summary>
7373
public bool HasMethod(string name) => _methodNames.Contains(name);
74+
75+
/// <summary>
76+
/// Returns a new <see cref="ObjectMemberInfo"/> identical to this one but with
77+
/// <paramref name="paramTypes"/> added as an additional constructor overload.
78+
/// </summary>
79+
internal ObjectMemberInfo WithExtraConstructorOverload(IReadOnlyList<string> paramTypes)
80+
{
81+
var ctors = ConstructorOverloads is null
82+
? new List<IReadOnlyList<string>> { paramTypes }
83+
: new List<IReadOnlyList<string>>(ConstructorOverloads) { paramTypes };
84+
return new ObjectMemberInfo(Properties, _methodNames, PropertyReturnTypes, ctors, MethodOverloads, ObsoletePropertyNames, ObsoleteMethodNames);
85+
}
7486
}

Catglobe.CgScript.EditorSupport.Parsing/SemanticAnalyzer.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,8 +1205,7 @@ private static bool IsCallValid(FunctionInfo funcInfo, string?[] argTypes)
12051205

12061206
/// <summary>
12071207
/// Returns <c>true</c> when at least one overload accepts the given argument types.
1208-
/// All parameters are treated as optional: a call with fewer args than an overload's
1209-
/// parameter count is valid as long as each supplied arg type matches.
1208+
/// The argument count must exactly match the overload's parameter count.
12101209
/// An overload whose last parameter type is <c>"Params object"</c> is variadic and
12111210
/// accepts any number of arguments beyond the preceding fixed parameters.
12121211
/// </summary>
@@ -1236,7 +1235,7 @@ private static bool IsAnyOverloadValid(
12361235
continue;
12371236
}
12381237

1239-
if (argTypes.Length > overload.Count) continue; // too many args for this overload
1238+
if (argTypes.Length != overload.Count) continue;
12401239
if (AllArgsCompatible(overload, argTypes)) return true;
12411240
}
12421241
return false;

Catglobe.CgScript.EditorSupport.SourceGenerator/CgScriptWrapperGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ private static string SanitizeHintName(string name)
290290
return sb.ToString();
291291
}
292292

293-
private static readonly CgScriptDefinitions _definitions = new();
293+
private static readonly CgScriptDefinitions _definitions = new CgScriptDefinitions().WithPreprocessorExtensions();
294294

295295
private static void ReportSemanticDiagnostics(
296296
SourceProductionContext spc,

0 commit comments

Comments
 (0)