Here's a working fix Claude gave me after describing the issue. It also handles other keywords with the same bug.
{ GX_CodeFormatterFormatter.pas - TCodeFormatterFormatter }{ interface section - new function: UsesUsedAsIdentifier }///<summary>/// @returns True if the current rtUses token ("uses"/"exports"/"requires"/"contains")/// is actually being used as an ordinary identifier rather than a section/// keyword. "contains" and "requires" are context-sensitive directives that/// are only keywords inside package (.dpk) files, so they are common/// identifiers (e.g. a method named "Contains") in normal code. </summary>functionUsesUsedAsIdentifier:Boolean;{ implementation section - new function: UsesUsedAsIdentifier }functionTCodeFormatterFormatter.UsesUsedAsIdentifier:Boolean;varNext:TPascalToken;Prev:TPascalToken;Idx:Integer;begin// A genuine "uses"/"exports"/"requires"/"contains" section keyword is always followed// by a unit identifier and only ever appears at the start of a declaration, so the// token preceding it is the program/unit header, "interface", "implementation" or ";".// It is never followed by "(" and never preceded by an operator, "(", "[" or ",".// "contains" and "requires" in particular are context-sensitive directives (keywords// only inside package .dpk files), so they are common identifiers in ordinary code.// a) Function/method call, e.g. "if not Contains(S) then" - a clause is never followed by "(".ifTryGetNextNoComment(FTokenIdx,Next)and(Next.ReservedType=rtLeftBr)thenbeginResult:=True;Exit;//==>end;// b) Used as an operand, e.g. "if not Contains then", "X := Requires", "(Contains)",// "Foo(A, Contains)". Find the previous meaningful token (skip line feeds and comments).Prev:=nil;Idx:=FTokenIdx-1;whileTryGetToken(Idx,Next)dobeginifnot(Next.ReservedTypein[rtLineFeed,rtComment])thenbeginPrev:=Next;Break;//==vend;Dec(Idx);end;Result:=(Prev<>nil)and(Prev.ReservedTypein[rtOper,rtMathOper,rtLogOper,rtMinus,rtPlus,rtEquals,rtAssignOper,rtLeftBr,rtLeftHook,rtComma]);end;{ implementation section - update function: CheckIndent }rtProgram,rtUses,rtInitialization:--if(FCurrentRType=rtUses)and(FStack.GetTopTypein[rtProcedure,rtProcDeclare,rtClass])thenbegin++if(FCurrentRType=rtUses)++and((FStack.GetTopTypein[rtProcedure,rtProcDeclare,rtClass])++orUsesUsedAsIdentifier)thenbegin++// The word is an ordinary identifier, not a "uses"/"exports"/"requires"/"contains"++// section keyword - treat it as a normal word. (The qualified case "obj.Contains"++// is already handled above where rtDot precedes the word.)FCurrentToken.SetReservedType(rtNothing);FCurrentRType:=rtNothing;
It also wrote a test file for this case (all /expected/ profiles share the same syntax, except special which has additional whitespace before not keyword in if conditional statements):
unittestfile_ContainsAsIdentifier;interfacetypeTMyClass=classprivatefunctionContains(constS:string):Boolean;functionRequires:Boolean;procedureDoAdd(constS:string);publicprocedureAddUnique(constS:string);procedureQualifiedCall(constS:string);functionBareIdentifiers:Boolean;end;implementationprocedureTMyClass.AddUnique(constS:string);beginifnotContains(S)thenDoAdd(S);end;procedureTMyClass.QualifiedCall(constS:string);beginifnotSelf.Contains(S)thenDoAdd(S);end;functionTMyClass.BareIdentifiers:Boolean;begin// contains/requires used as bare identifiers (no parens, no leading dot)ifnotContainsthenDoAdd('x');Result:=Requiresand(notContains);end;end.
Last edit: Luka Havrlišan 2026-05-30
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Here's a working fix Claude gave me after describing the issue. It also handles other keywords with the same bug.
It also wrote a test file for this case (all /expected/ profiles share the same syntax, except special which has additional whitespace before
notkeyword in if conditional statements):Last edit: Luka Havrlišan 2026-05-30
fixed in revision 5467 (using your code as basis, thanks)