Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
1f46ab2
LDEV-5977 fix AST ternary alternate using wrong branch
zspitzer Dec 11, 2025
9c15bb9
LDEV-5969 preserve tag attributes in AST after removeAttribute()
zspitzer Dec 11, 2025
e26fd39
LDEV-5975 add test for chained method calls in AST
zspitzer Dec 11, 2025
eb9cc45
LDEV-5978 add test for queryExecute extra arg in AST
zspitzer Dec 11, 2025
638cf92
LDEV-5979 add test for isDefined extra arg in AST
zspitzer Dec 11, 2025
07ff462
LDEV-5980 add test for BIF internal metadata in AST
zspitzer Dec 11, 2025
57b2d63
LDEV-5975 preserve original arguments in AST before evaluator modific…
zspitzer Dec 11, 2025
c433562
LDEV-5982 snapshot tag attributes before evaluators in AST mode
zspitzer Dec 11, 2025
f47167d
LDEV-5983 fix cffunction body duplicating comments between cfargument…
zspitzer Dec 11, 2025
b88211e
LDEV-5985 fix ClassCastException when parsing Java functions in AST mode
zspitzer Dec 11, 2025
d93e94f
LDEV-5987 fix missing parent statement error with thread tag in AST mode
zspitzer Dec 11, 2025
61177a8
LDEV-5984 escape hashes in StringLiteral raw for AST round-tripping
zspitzer Dec 11, 2025
16e055d
LDEV-5986 add TagIsland node type for tag islands in AST
zspitzer Dec 11, 2025
2861320
LDEV-5988 handle null string in LitStringImpl.dump() for AST mode
zspitzer Dec 11, 2025
a0e2970
LDEV-5715 validate mode parameter in astFromString()
zspitzer Dec 11, 2025
db725e2
LDEV-5989 preserve original source in AST raw field for SimpleExprTra…
zspitzer Dec 11, 2025
bf982cb
LDEV-5990: Function param hint and defaultValue missing from AST
zspitzer Dec 11, 2025
1d4bf6e
LDEV-5895 AST include java function source
zspitzer Dec 15, 2025
f48c720
LDEV-6008 PreserveSingleQuotes evaluated in cfquery AST
zspitzer Dec 17, 2025
4ef4683
LDEV-6007 AST Component inside cfscript becomes sibling instead of child
zspitzer Dec 17, 2025
0999067
LDEV-6007 AST unwrap script components from cfscript wrapper
zspitzer Dec 18, 2025
0398dd6
LDEV-6006 update tests for script mode astFromString
zspitzer Dec 18, 2025
ff84a35
LDEV-6009 add compiler metadata to AST Program node
zspitzer Dec 18, 2025
ba701ab
LDEV-6011 AST uses proper node types for literals
zspitzer Dec 18, 2025
c101c5e
LDEV-6013 AST MemberExpression computed flag for bracket notation
zspitzer Dec 18, 2025
327b4e2
LDEV-6014 fix DynAssign AST dump for dynamic property assignment
zspitzer Dec 18, 2025
96e2471
LDEV-6007 fix astFromPath for cfscript-wrapped components
zspitzer Dec 18, 2025
3d5394a
LDEV-5982 snapshot attributes in script mode too
zspitzer Dec 18, 2025
b72d2e2
LDEV-6015 add quoteChar field to AST StringLiteral
zspitzer Dec 18, 2025
bd018fa
LDEV-6016 add static and final markers to AST output
zspitzer Dec 18, 2025
46ef986
LDEV-5990 add hintSource field to AST FunctionDeclaration
zspitzer Dec 19, 2025
8bade21
LDEV-6019 allow unquoted function attributes in AST mode
zspitzer Dec 19, 2025
a230809
LDEV-6009 fix sourceType for script components with tag islands
zspitzer Dec 19, 2025
473ddf9
LDEV-6017 AST interpolated strings use TemplateLiteral instead of Bin…
zspitzer Dec 19, 2025
37fae18
LDEV-6018 AST unwrap CastExpression wrapper from concat operands
zspitzer Dec 19, 2025
bbaf092
LDEV-6022 preserve string literal concatenation in AST
zspitzer Dec 19, 2025
8aa46c0
LDEV-6024 fix computed member expression in scope access AST
zspitzer Dec 19, 2025
0c9ca8e
LDEV-5989 AST parses interpolated rtexprvalue=false attributes as exp…
zspitzer Dec 19, 2025
35bc733
LDEV-6027 preserve property attribute declaration order in AST
zspitzer Dec 19, 2025
bee9f12
LDEV-6015 add quoteChar to TemplateLiteral AST nodes
zspitzer Dec 19, 2025
bbaa2f5
LDEV-6015 update astFromString tests for quoteChar field
zspitzer Dec 19, 2025
87acdf2
LDEV-6016 don't merge static blocks in AST
zspitzer Dec 19, 2025
a2c2de6
LDEV-6028 preserve struct key AST type regardless of separator
zspitzer Dec 19, 2025
2ab33fd
LDEV-6029 preserve cachedWithin expression in script function AST
zspitzer Dec 19, 2025
414d8cd
LDEV-6030 include label property in loop AST nodes
zspitzer Dec 20, 2025
d89ee22
LDEV-6031 fix cfimport custom tag AST output
zspitzer Dec 20, 2025
01b4054
LDEV-6030 skip null label attribute for cfbreak/cfcontinue in AST
zspitzer Dec 20, 2025
6b2febf
LDEV-6016 preserve static block/function position in AST
zspitzer Dec 20, 2025
1987785
LDEV-6032 parse unquoted boolean attribute values as BooleanLiteral i…
zspitzer Dec 20, 2025
ff8b89d
LDEV-6011 track separator character in named arguments and struct pro…
zspitzer Dec 21, 2025
47eacab
LDEV-6018 fix AST output for unary plus, integer divide, safe navigat…
zspitzer Dec 21, 2025
8027b72
LDEV-6018 fix safe navigation optional flag bleeding to previous member
zspitzer Dec 21, 2025
7e23764
LDEV-6034 hide auto-generated names from closure/lambda AST output
zspitzer Dec 21, 2025
b7cee55
LDEV-6018 output UpdateExpression for increment/decrement operators
zspitzer Dec 21, 2025
dbb4e4b
LDEV-6035 include fullname for shorthand script tags
zspitzer Dec 21, 2025
e2c75a2
LDEV-5990 preserve raw docblock and metadata in AST
zspitzer Dec 21, 2025
9f91f7b
LDEV-5990 clean AST separation: docblock→annotations, inline attrs→me…
zspitzer Dec 21, 2025
27490ad
LDEV-6036 mark default type=any as default attribute in script proper…
zspitzer Dec 21, 2025
33ab638
LDEV-6011 preserve :: static method syntax in AST output
zspitzer Dec 22, 2025
1358f0d
LDEV-5990 fix docblock attached to closure default value instead of o…
zspitzer Dec 22, 2025
15bdff3
LDEV-6015 ExpressionInvoker dump() for member expression chains
zspitzer Dec 22, 2025
8fccc3b
LDEV-6032 parse named attributes correctly for single-type script tags
zspitzer Dec 22, 2025
b9665c7
LDEV-6032 naked boolean attributes in script mode should be BooleanLi…
zspitzer Dec 22, 2025
652d5a0
LDEV-6032 preserve NULL default for positional attributes in property…
zspitzer Dec 22, 2025
f680b64
LDEV-6038 prevent ghost nodes after trailing comments in tag mode
zspitzer Dec 22, 2025
d3ab210
LDEV-6032 support parenthesized named attributes in single-type scrip…
zspitzer Dec 22, 2025
aa4bce7
LDEV-6015 preserve position for unquoted function attribute values
zspitzer Dec 22, 2025
de8df70
LDEV-6011 add separator field to Attribute for AST round-tripping
zspitzer Dec 22, 2025
2930fc0
LDEV-5990 arrow functions should not consume outer function docblock
zspitzer Dec 22, 2025
f6e7034
LDEV-6041 preserve source fidelity in AST for var keyword and identif…
zspitzer Dec 23, 2025
420c11f
LDEV-6036 add accessExplicit flag for AST source fidelity
zspitzer Dec 25, 2025
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
8 changes: 8 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(curl:*)"
]
}
}
87 changes: 75 additions & 12 deletions core/src/main/java/lucee/runtime/compiler/CFMLCompilerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import lucee.runtime.exp.TemplateException;
import lucee.runtime.op.Caster;
import lucee.runtime.type.Array;
import lucee.runtime.type.ArrayImpl;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.util.KeyConstants;
Expand Down Expand Up @@ -84,32 +85,40 @@ public Struct ast(ConfigPro config, PageSource ps, boolean ignoreScopes) throws

BytecodeFactory factory = BytecodeFactory.getInstance(config);
// , cwi.getFLDs()
PageImpl page = ((PageImpl) cfmlTagTransformer.transform(factory, config, ps, config.getTLDs(), config.getFLDs(), false, ignoreScopes));
PageImpl page = ((PageImpl) cfmlTagTransformer.transform(factory, config, ps, config.getTLDs(), config.getFLDs(), false, ignoreScopes, true));
Struct root = new StructImpl(Struct.TYPE_LINKED);
page.dump(root);

// TODO better solution than simply look at the offset from script
if (page.getSourceCode().getSourceOffset() == 10) {
boolean isScript = page.getSourceCode().isWrappedInScript();
boolean isCFMLCompExt = Constants.isCFMLComponentExtension(ResourceUtil.getExtension(ps.getResource(), ""));

boolean isCFMLCompExt = Constants.isCFMLComponentExtension(ResourceUtil.getExtension(ps.getResource(), ""));
// If parser wrapped script content in cfscript tags, unwrap it in the AST output
if (isScript) {
// in case of a component Lucee moves the component to the root, so at the first position is just an
// empty script, we simply have to emove this
// TODO remove the script after moving in the parser
// empty script, we simply have to remove this
if (isCFMLCompExt) {
removeEmptyScriptTag(root);
}
else {
extractScriptTagInRoot(root);
}
}
// Handle <cfscript>component { }</cfscript> pattern - file wasn't wrapped by parser
// but component is inside an explicit cfscript tag that needs unwrapping
else if (isCFMLCompExt) {
extractComponentFromCfscript(root);
}

// Add compiler metadata to AST root
addASTMetadata(root, config, isScript);

return root;
}

public Struct ast(ConfigPro config, SourceCode sc, boolean ignoreScopes, Boolean script) throws PageException {
BytecodeFactory factory = BytecodeFactory.getInstance(config);
// TODO auto when script is null

if (script != null && script) {
if (script) {
TagLibTag scriptTag = CFMLTransformer.getTLT(sc, Constants.CFML_SCRIPT_TAG_NAME, config.getIdentification());

sc.setPos(0);
Expand All @@ -124,13 +133,23 @@ public Struct ast(ConfigPro config, SourceCode sc, boolean ignoreScopes, Boolean
Struct root = new StructImpl(Struct.TYPE_LINKED);
page.dump(root);

if (script != null && script) {
if (script) {
extractScriptTagInRoot(root);
}

// Add compiler metadata to AST root
addASTMetadata(root, config, script);

return root;
}

// Add compiler settings metadata to the AST Program node
private void addASTMetadata(Struct root, ConfigPro config, boolean isScript) {
root.setEL("sourceType", isScript ? "script" : "tag");
root.setEL("dotNotationUpperCase", config.getDotNotationUpperCase());
root.setEL("handleUnquotedAttrValueAsString", config.getHandleUnQuotedAttrValueAsString());
}

// remove script again (a bit complicated, but atm the only way to do it)
private void extractScriptTagInRoot(Struct root) {
Array body = Caster.toArray(root.get(KeyConstants._body, null), null);
Expand All @@ -150,14 +169,58 @@ private void extractScriptTagInRoot(Struct root) {

private void removeEmptyScriptTag(Struct root) {
Array body = Caster.toArray(root.get(KeyConstants._body, null), null);
if (body != null && body.size() > 1) {
if (body != null && body.size() >= 1) {
Struct first = Caster.toStruct(body.get(1, null), null);
if (first != null) {
if ("cfscript".equalsIgnoreCase(Caster.toString(first.get("fullname", null), null))) {
Struct body2 = Caster.toStruct(first.get(KeyConstants._body, null), null);
Array body3 = Caster.toArray(body2.get(KeyConstants._body, null), null);
if (body3 != null && body3.size() == 0) {
body.removeEL(1);
if (body3 != null) {
if (body3.size() == 0) {
// Empty cfscript - just remove it
body.removeEL(1);
}
else {
// cfscript has content (component) - extract it
// Replace root body with cfscript contents
root.setEL(KeyConstants._body, body3);
}
}
}
}
}
}

// Extract component from explicit <cfscript>component { }</cfscript> wrapper
// This handles CFC files where the component is wrapped in cfscript tags by the author
private void extractComponentFromCfscript(Struct root) {
Array body = Caster.toArray(root.get(KeyConstants._body, null), null);
if (body == null || body.size() < 1) return;

// Look for cfscript tag containing a component
for (int i = 1; i <= body.size(); i++) {
Struct item = Caster.toStruct(body.get(i, null), null);
if (item == null) continue;

if ("cfscript".equalsIgnoreCase(Caster.toString(item.get("fullname", null), null))) {
Struct scriptBody = Caster.toStruct(item.get(KeyConstants._body, null), null);
if (scriptBody == null) continue;

Array scriptContents = Caster.toArray(scriptBody.get(KeyConstants._body, null), null);
if (scriptContents == null) continue;

// Find component inside cfscript body (CFMLTag with name "component" or "interface")
for (int j = 1; j <= scriptContents.size(); j++) {
Struct child = Caster.toStruct(scriptContents.get(j, null), null);
if (child == null) continue;

String name = Caster.toString(child.get(KeyConstants._name, null), null);
if ("component".equalsIgnoreCase(name) || "interface".equalsIgnoreCase(name)) {
// Found component - replace root body with just the component
Array newBody = new ArrayImpl();
newBody.appendEL(child);
root.setEL(KeyConstants._body, newBody);
return;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ public Object invoke(PageContext pc, Object[] args) throws PageException {
Boolean script = Boolean.FALSE;
if (args.length == 2) {
String mode = Caster.toString(args[1], null);
//
if ("script".equalsIgnoreCase(mode)) script = Boolean.TRUE;

if ("script".equalsIgnoreCase(mode)) {
script = Boolean.TRUE;
}
else if (mode != null && !"tag".equalsIgnoreCase(mode)) {
throw new FunctionException(pc, "AstFromString", 2, "mode", "invalid mode [" + mode + "], valid values are [tag, script]");
}
}

return ((PageContextImpl) pc).transform(new SourceCode(null, content, false, 0), script);
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/lucee/runtime/type/util/KeyConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ private static Collection.Key init(String key) {
public static final Key _dc_subject = init("dc_subject");
public static final Key _debug = init("debug");
public static final Key _debugging = init("debugging");
public static final Key _declaration = init("declaration");
public static final Key _decorator = init("decorator");
public static final Key _default = init("default");
public static final Key _delete = init("delete");
Expand Down Expand Up @@ -757,6 +758,8 @@ private static Collection.Key init(String key) {
public static final Key _statuscode = init("statuscode");
public static final Key _statustext = init("statustext");
public static final Key _extends = init("extends");
public static final Key _explicit = init("explicit");
public static final Key _accessExplicit = init("accessExplicit");
public static final Key _implements = init("implements");
public static final Key __toDateTime = init("_toDateTime");
public static final Key __toNumeric = init("_toNumeric");
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/lucee/transformer/Factory.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ public abstract class Factory {

public abstract ExprString opString(Expression left, Expression right, boolean concatStatic);

public abstract ExprString opStringInterpolation(Expression left, Expression right);

public abstract ExprBoolean opBool(Expression left, Expression right, int operation);

public abstract ExprNumber opNumber(Expression left, Expression right, int operation);
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/lucee/transformer/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface Page extends Root {

public boolean isComponent();

public boolean isAST();

public SourceCode getSourceCode();

public Config getConfig();
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/lucee/transformer/bytecode/BodyBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ public void addStatement(Statement statement) {

if (statement instanceof PrintOut) {
Expression expr = ((PrintOut) statement).getExpr();
if (expr instanceof LitString && concatPrintouts(((LitString) expr).getString())) return;
// LDEV-5983: Only concatenate if the statement doesn't already belong to another body.
// Otherwise concatenating modifies the original PrintOut which may still be used elsewhere.
if (expr instanceof LitString && statement.getParent() == null && concatPrintouts(((LitString) expr).getString())) return;
}
statement.setParent(this);
this.statements.add(statement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,21 @@ public Variable createVariable(int scope, Position start, Position end) {

@Override
public ExprString opString(Expression left, Expression right) {
return OpString.toExprString(left, right, true);
// Pass false to preserve string literal structure for AST output (LDEV-6022)
// Otherwise "<c" & "fsc" merges to "<cfsc" which breaks re-parsing
return OpString.toExprString(left, right, false);
}

@Override
public ExprString opString(Expression left, Expression right, boolean concatStatic) {
return OpString.toExprString(left, right, concatStatic);
}

@Override
public ExprString opStringInterpolation(Expression left, Expression right) {
return OpString.toExprStringInterpolation(left, right);
}

@Override
public ExprBoolean opBool(Expression left, Expression right, int operation) {
return OpBool.toExprBoolean(left, right, operation);
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/lucee/transformer/bytecode/PageImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ public final class PageImpl extends BodyBase implements Page {
private int hash;
private List<JavaFunction> javaFunctions;
private Set<String> javaFunctionNames;
private boolean forAST;

/**
* @param factory
Expand Down Expand Up @@ -1763,6 +1764,15 @@ public boolean isPage() {
return getTagCFObject(null) == null;
}

@Override
public boolean isAST() {
return forAST;
}

public void setAST(boolean forAST) {
this.forAST = forAST;
}

/**
* @return the lastModifed
*/
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/lucee/transformer/bytecode/StaticBody.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,27 @@
*/
package lucee.transformer.bytecode;

import lucee.runtime.type.Struct;
import lucee.transformer.Factory;
import lucee.transformer.Position;

public final class StaticBody extends BodyBase {

public StaticBody(Factory f) {
super(f);
}

public StaticBody(Factory f, Position start, Position end) {
super(f);
setStart(start);
setEnd(end);
}

@Override
public void dump(Struct sct) {
super.dump(sct);
// Mark this block as a static initializer
sct.setEL("static", Boolean.TRUE);
}

}
Loading
Loading