Skip to content

Commit 2e39dfe

Browse files
fix(cli): split comma-joined action tokens in generate controller
`wheels generate controller Name a,b` passed the comma-joined token through verbatim, emitting an invalid `function a,b()` method plus an `a,b.cfm` view file. Normalize the action list in CodeGen.generateController by splitting each positional token on commas, trimming, and de-duplicating, and return the normalized list so Module.cfc renders one view file per real action. The comma form now behaves identically to the documented space-separated form. Fixes #3112 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 2fb562d commit 2e39dfe

4 files changed

Lines changed: 70 additions & 1 deletion

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `wheels generate controller Name a,b` now splits the comma-joined action token into discrete actions (`a` and `b`) instead of silently emitting an invalid `function a,b()` method plus an `a,b.cfm` view file — the comma form now matches the documented space-separated form (#3112)

cli/lucli/Module.cfc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3190,6 +3190,11 @@ component extends="modules.BaseModule" {
31903190
return "";
31913191
}
31923192

3193+
// Use the normalized action list the generator actually wrote (comma-joined
3194+
// tokens like "index,show" are split into discrete actions) so view files
3195+
// match the controller methods instead of being named "index,show.cfm" (#3112).
3196+
actions = result.actions;
3197+
31933198
// Create view files for non-mutation actions
31943199
var viewDir = variables.projectRoot & "/app/views/#lCase(controllerName)#";
31953200
ensureDirectory(viewDir);

cli/lucli/services/CodeGen.cfc

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ component {
142142

143143
var crudActions = ["index", "show", "new", "create", "edit", "update", "delete"];
144144

145+
// Normalize the action list: a comma-joined token like "index,show" is the
146+
// natural guess for anyone used to Wheels list args (validatesPresenceOf("a,b")),
147+
// but passed through verbatim it produced `function index,show()` — invalid CFML
148+
// that fails to compile — plus a view file named `index,show.cfm` (#3112). Split
149+
// each token on commas, trim, and de-duplicate so both forms behave identically.
150+
arguments.actions = normalizeActions(arguments.actions);
151+
145152
// Default actions based on type
146153
if (arrayLen(arguments.actions) == 0) {
147154
arguments.actions = arguments.crud ? crudActions : ["index"];
@@ -171,11 +178,36 @@ component {
171178
template = hasCustomActions ? "ControllerContent.txt" : "CRUDContent.txt";
172179
}
173180

174-
return variables.templateService.generateFromTemplate(
181+
var result = variables.templateService.generateFromTemplate(
175182
template = template,
176183
destination = relativePath,
177184
context = context
178185
);
186+
187+
// Surface the normalized action list so callers (e.g. Module.cfc's view loop)
188+
// render one view file per real action instead of one named after the raw
189+
// comma-joined token (#3112).
190+
result.actions = arguments.actions;
191+
return result;
192+
}
193+
194+
/**
195+
* Flatten a positional action list into discrete, trimmed, de-duplicated action
196+
* names. Splits comma-joined tokens ("index,show" -> ["index","show"]) so the comma
197+
* form matches the documented space-separated form, drops empties, and preserves
198+
* first-seen order. Comparison is case-insensitive but the original casing is kept (#3112).
199+
*/
200+
private array function normalizeActions(required array actions) {
201+
var normalized = [];
202+
for (var token in arguments.actions) {
203+
for (var part in listToArray(token, ",")) {
204+
var trimmed = trim(part);
205+
if (len(trimmed) && !arrayFindNoCase(normalized, trimmed)) {
206+
arrayAppend(normalized, trimmed);
207+
}
208+
}
209+
}
210+
return normalized;
179211
}
180212

181213
/**

cli/lucli/tests/specs/services/CodeGenSpec.cfc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,37 @@ component extends="wheels.wheelstest.system.BaseSpec" {
219219
expect(content).toInclude('extends="Controller"');
220220
});
221221

222+
it("splits a comma-joined action token into separate actions (##3112)", () => {
223+
var result = codegen.generateController(
224+
name = "Authors",
225+
actions = ["index,show"],
226+
force = true
227+
);
228+
var content = fileRead(tempRoot & "/app/controllers/Authors.cfc");
229+
// The bug: one element "index,show" emitted as `function index,show()`
230+
expect(content).notToInclude("index,show");
231+
expect(content).toInclude("function index()");
232+
expect(content).toInclude("function show()");
233+
});
234+
235+
it("returns the normalized action list so callers render correct views (##3112)", () => {
236+
var result = codegen.generateController(
237+
name = "Editors",
238+
actions = ["index, show ,create"],
239+
force = true
240+
);
241+
expect(result.actions).toBe(["index", "show", "create"]);
242+
});
243+
244+
it("de-duplicates and trims actions from comma tokens (##3112)", () => {
245+
var result = codegen.generateController(
246+
name = "Curators",
247+
actions = ["index", "show,index"],
248+
force = true
249+
);
250+
expect(result.actions).toBe(["index", "show"]);
251+
});
252+
222253
});
223254

224255
describe("validateName()", () => {

0 commit comments

Comments
 (0)