Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions cli/lucli/services/CodeGen.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,18 @@ component {
arrayAppend(presenceProps, prop.name);
var propType = structKeyExists(prop, "type") ? lCase(prop.type) : "string";
if (propType == "email") {
arrayAppend(formatLines, " validatesFormatOf(property=""#prop.name#"", type=""email"");");
arrayAppend(formatLines, "validatesFormatOf(property=""#prop.name#"", type=""email"");");
} else if (propType == "url") {
arrayAppend(formatLines, " validatesFormatOf(property=""#prop.name#"", type=""URL"");");
arrayAppend(formatLines, "validatesFormatOf(property=""#prop.name#"", type=""URL"");");
}
}

var lines = [" validatesPresenceOf(""#arrayToList(presenceProps)#"");"];
var lines = ["validatesPresenceOf(""#arrayToList(presenceProps)#"");"];
lines.append(formatLines, true);
return arrayToList(lines, chr(10));
// Join with newline + 2 tabs so subsequent lines align with the template's
// `\t\t{{validations}}` placeholder indent. The first line gets its indent
// from the placeholder's leading whitespace at fill time.
return arrayToList(lines, chr(10) & chr(9) & chr(9));
}

/**
Expand Down
35 changes: 33 additions & 2 deletions cli/lucli/services/Templates.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,37 @@ component {
processed = replace(processed, "|ObjectNamePluralC|", variables.helpers.pluralize(modelName), "all");
}

// Final cleanup: remove orphan whitespace-only lines left behind by
// empty placeholders (e.g. `\t\t{{hasManyRelationships}}` when no
// hasMany relationships exist), and collapse runs of consecutive blank
// lines to a single blank line. Without this, generated files end up
// with 4+ blank lines inside config() — see issue #2329.
processed = collapseEmptyLines(processed);

return processed;
}

/**
* Normalise whitespace-only lines to genuinely empty, then collapse runs
* of 2+ consecutive empty lines down to one. Used after placeholder
* substitution to clean up template-fill leftovers.
*/
private string function collapseEmptyLines(required string content) {
var lines = listToArray(arguments.content, chr(10), true);
var cleaned = [];
var prevWasEmpty = false;

for (var line in lines) {
var normalised = reFind("^[[:space:]]+$", line) ? "" : line;
var isEmpty = (normalised == "");
if (isEmpty && prevWasEmpty) continue;
cleaned.append(normalised);
prevWasEmpty = isEmpty;
}

return arrayToList(cleaned, chr(10));
}

// ── Private helpers ──────────────────────────────

/**
Expand Down Expand Up @@ -275,9 +303,12 @@ component {
private string function generateRelationshipCode(required string type, required string names) {
var code = [];
for (var rel in listToArray(arguments.names)) {
arrayAppend(code, " #arguments.type#('#trim(rel)#');");
arrayAppend(code, "#arguments.type#('#trim(rel)#');");
}
return arrayToList(code, chr(10));
// Join with newline + 2 tabs so subsequent lines align with the template's
// `\t\t{{...Relationships}}` placeholder indent. The first line gets its
// indent from the placeholder's leading whitespace at fill time.
return arrayToList(code, chr(10) & chr(9) & chr(9));
}

/**
Expand Down
58 changes: 58 additions & 0 deletions cli/lucli/tests/specs/services/CodeGenSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,64 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(content).notToInclude("validatesFormatOf");
});

it("produces no orphan whitespace-only lines (##2329)", () => {
codegen.generateModel(
name = "Layout",
properties = [{name: "title", type: "string"}],
force = true
);
var content = fileRead(tempRoot & "/app/models/Layout.cfc");
var lines = listToArray(content, chr(10), true);
var whitespaceOnly = [];
for (var i = 1; i <= arrayLen(lines); i++) {
if (reFind("^[[:space:]]+$", lines[i])) {
arrayAppend(whitespaceOnly, "line " & i & ": '" & lines[i] & "'");
}
}
expect(arrayLen(whitespaceOnly)).toBe(0);
});

it("produces no consecutive blank-line runs (##2329)", () => {
codegen.generateModel(
name = "Spacer",
properties = [{name: "name", type: "string"}],
force = true
);
var content = fileRead(tempRoot & "/app/models/Spacer.cfc");
// 3+ consecutive newlines = 2+ consecutive blank lines
expect(content).notToInclude(chr(10) & chr(10) & chr(10));
});

it("indents validations at 2 tabs, not 4 (##2329)", () => {
codegen.generateModel(
name = "Indent",
properties = [{name: "title", type: "string"}],
force = true
);
var content = fileRead(tempRoot & "/app/models/Indent.cfc");
// 2-tab indent is correct
expect(content).toInclude(chr(9) & chr(9) & "validatesPresenceOf");
// 4-tab indent is the bug shape — must not be present
expect(content).notToInclude(chr(9) & chr(9) & chr(9) & chr(9) & "validatesPresenceOf");
});

it("indents multi-line validations consistently at 2 tabs (##2329)", () => {
codegen.generateModel(
name = "MultiVal",
properties = [
{name: "name", type: "string"},
{name: "email", type: "email"}
],
force = true
);
var content = fileRead(tempRoot & "/app/models/MultiVal.cfc");
// Both lines must be at the same 2-tab indent
expect(content).toInclude(chr(9) & chr(9) & "validatesPresenceOf");
expect(content).toInclude(chr(9) & chr(9) & "validatesFormatOf");
// And neither line should be at column 0 (subsequent-line bug)
expect(content).notToInclude(chr(10) & "validatesFormatOf");
});

});

describe("generateController()", () => {
Expand Down