mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-10 15:36:01 +02:00
Codetools: Testcase, add a few options for upcoming tests
This commit is contained in:
parent
de1209eefb
commit
47177f39ca
@ -21,6 +21,31 @@
|
|||||||
./testcodetools --suite=TestFindDeclaration_LazTests --filemask=t*.pp
|
./testcodetools --suite=TestFindDeclaration_LazTests --filemask=t*.pp
|
||||||
./testcodetools --suite=TestFindDeclaration_LazTests --filemask=tdefaultproperty1.pp
|
./testcodetools --suite=TestFindDeclaration_LazTests --filemask=tdefaultproperty1.pp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(* Expectation in test-files
|
||||||
|
|
||||||
|
{SELECT:TESTS=TEST(|TEST)*}
|
||||||
|
Each "{" comment starting with a..z is a test instruction, or a list of test instructions separated by |
|
||||||
|
|
||||||
|
SELECT can be one of the following tests:
|
||||||
|
|
||||||
|
{completion:TESTS}
|
||||||
|
TEST=([+-]POS=)?ENTRY(;ENTRY)*
|
||||||
|
Tests: CodeToolBoss.GatherIdentifiers
|
||||||
|
|
||||||
|
Each TEST can start with an optional POS (integer positive/negative)
|
||||||
|
The POS specifies the relative source-pos from the start of the identifier before the comment.
|
||||||
|
|
||||||
|
Each ENTRY can start with a ! to test for a non-present completion
|
||||||
|
|
||||||
|
{declaration:
|
||||||
|
Tests: CodeToolBoss.FindDeclaration
|
||||||
|
Also runs {completion:*}
|
||||||
|
|
||||||
|
{guesstype:
|
||||||
|
Tests: CodeToolBoss.GuessTypeOfIdentifier
|
||||||
|
|
||||||
|
*)
|
||||||
unit TestFindDeclaration;
|
unit TestFindDeclaration;
|
||||||
|
|
||||||
{$i runtestscodetools.inc}
|
{$i runtestscodetools.inc}
|
||||||
@ -35,7 +60,7 @@ uses
|
|||||||
FileProcs, LazFileUtils, LazLogger,
|
FileProcs, LazFileUtils, LazLogger,
|
||||||
CodeToolManager, ExprEval, CodeCache, BasicCodeTools,
|
CodeToolManager, ExprEval, CodeCache, BasicCodeTools,
|
||||||
CustomCodeTool, CodeTree, FindDeclarationTool, KeywordFuncLists,
|
CustomCodeTool, CodeTree, FindDeclarationTool, KeywordFuncLists,
|
||||||
IdentCompletionTool, DefineTemplates, TestPascalParser;
|
IdentCompletionTool, DefineTemplates, StrUtils, TestPascalParser;
|
||||||
|
|
||||||
const
|
const
|
||||||
MarkDecl = '#'; // a declaration, must be unique
|
MarkDecl = '#'; // a declaration, must be unique
|
||||||
@ -240,10 +265,11 @@ var
|
|||||||
FoundPath: String;
|
FoundPath: String;
|
||||||
Src: String;
|
Src: String;
|
||||||
NameStartPos, i, l, IdentifierStartPos, IdentifierEndPos,
|
NameStartPos, i, l, IdentifierStartPos, IdentifierEndPos,
|
||||||
BlockTopLine, BlockBottomLine: Integer;
|
BlockTopLine, BlockBottomLine, CommentEnd, StartOffs: Integer;
|
||||||
Marker, ExpectedType, NewType: String;
|
Marker, ExpectedType, NewType, ExpexctedCompletion, ExpexctedTerm,
|
||||||
|
ExpexctedCompletionPart, ExpexctedTermPart: String;
|
||||||
IdentItem: TIdentifierListItem;
|
IdentItem: TIdentifierListItem;
|
||||||
ItsAKeyword, IsSubIdentifier: boolean;
|
ItsAKeyword, IsSubIdentifier, ExpInvert: boolean;
|
||||||
ExistingDefinition: TFindContext;
|
ExistingDefinition: TFindContext;
|
||||||
ListOfPFindContext: TFPList;
|
ListOfPFindContext: TFPList;
|
||||||
NewExprType: TExpressionType;
|
NewExprType: TExpressionType;
|
||||||
@ -260,8 +286,11 @@ begin
|
|||||||
if Src[p]<>'{' then continue;
|
if Src[p]<>'{' then continue;
|
||||||
if Src[p+1] in ['$','%',' ',#0..#31] then continue;
|
if Src[p+1] in ['$','%',' ',#0..#31] then continue;
|
||||||
|
|
||||||
IdentifierStartPos:=p;
|
// allow spaces before the comment
|
||||||
IdentifierEndPos:=p;
|
IdentifierEndPos:=p;
|
||||||
|
while (IdentifierEndPos>1) and (IsSpaceChar[Src[IdentifierEndPos-1]]) do
|
||||||
|
dec(IdentifierEndPos);
|
||||||
|
IdentifierStartPos:=IdentifierEndPos;
|
||||||
while (IdentifierStartPos>1) and (IsIdentChar[Src[IdentifierStartPos-1]]) do
|
while (IdentifierStartPos>1) and (IsIdentChar[Src[IdentifierStartPos-1]]) do
|
||||||
dec(IdentifierStartPos);
|
dec(IdentifierStartPos);
|
||||||
if IdentifierStartPos=p then begin
|
if IdentifierStartPos=p then begin
|
||||||
@ -284,123 +313,163 @@ begin
|
|||||||
continue;
|
continue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// check for specials:
|
CommentEnd := CommentP;
|
||||||
{declaration:path}
|
CommentP := p-1;
|
||||||
{guesstype:type}
|
repeat
|
||||||
if not IsIdentStartChar[Src[p]] then continue;
|
NameStartPos:=CommentP+1;
|
||||||
while (p<=length(Src)) and (IsIdentChar[Src[p]]) do inc(p);
|
p := NameStartPos;
|
||||||
Marker:=copy(Src,NameStartPos,p-NameStartPos);
|
CommentP := PosEx('|', Src, NameStartPos);
|
||||||
if (p>length(Src)) or (Src[p]<>':') then begin
|
if (CommentP < 1) or (CommentP > CommentEnd) then
|
||||||
WriteSource(p,MainTool);
|
CommentP := CommentEnd;
|
||||||
AssertEquals('Expected : at '+MainTool.CleanPosToStr(p,true),'declaration',Marker);
|
|
||||||
continue;
|
|
||||||
end;
|
|
||||||
inc(p);
|
|
||||||
PathPos:=p;
|
|
||||||
|
|
||||||
//debugln(['TTestFindDeclaration.FindDeclarations Marker="',Marker,'" params: ',dbgstr(MainTool.Src,p,CommentP-p)]);
|
// check for specials:
|
||||||
if (Marker='declaration') then begin
|
{declaration:path}
|
||||||
ExpectedPath:=copy(Src,PathPos,CommentP-1-PathPos);
|
{guesstype:type}
|
||||||
{$IFDEF VerboseFindDeclarationTests}
|
if not IsIdentStartChar[Src[p]] then continue;
|
||||||
debugln(['TTestFindDeclaration.FindDeclarations searching "',Marker,'" at ',MainTool.CleanPosToStr(NameStartPos-1),' ExpectedPath=',ExpectedPath]);
|
while (p<=length(Src)) and (IsIdentChar[Src[p]]) do inc(p);
|
||||||
{$ENDIF}
|
Marker:=copy(Src,NameStartPos,p-NameStartPos);
|
||||||
MainTool.CleanPosToCaret(IdentifierStartPos,CursorPos);
|
if (p>length(Src)) or (Src[p]<>':') then begin
|
||||||
|
WriteSource(p,MainTool);
|
||||||
// test FindDeclaration
|
AssertEquals('Expected : at '+MainTool.CleanPosToStr(p,true),'declaration',Marker);
|
||||||
if not CodeToolBoss.FindDeclaration(CursorPos.Code,CursorPos.X,CursorPos.Y,
|
|
||||||
FoundCursorPos.Code,FoundCursorPos.X,FoundCursorPos.Y,FoundTopLine,
|
|
||||||
BlockTopLine,BlockBottomLine)
|
|
||||||
then begin
|
|
||||||
if ExpectedPath<>'' then begin
|
|
||||||
//if (CodeToolBoss.ErrorCode<>nil) then begin
|
|
||||||
//ErrorTool:=CodeToolBoss.GetCodeToolForSource(CodeToolBoss.ErrorCode);
|
|
||||||
//if ErrorTool<>MainTool then
|
|
||||||
// WriteSource(,ErrorTool);
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
Fail('find declaration failed at '+MainTool.CleanPosToStr(IdentifierStartPos,true)+': '+CodeToolBoss.ErrorMessage);
|
|
||||||
end;
|
|
||||||
continue;
|
continue;
|
||||||
|
end;
|
||||||
|
inc(p);
|
||||||
|
PathPos:=p;
|
||||||
|
|
||||||
|
//debugln(['TTestFindDeclaration.FindDeclarations Marker="',Marker,'" params: ',dbgstr(MainTool.Src,p,CommentP-p)]);
|
||||||
|
if (Marker='declaration') or (Marker='completion') then begin
|
||||||
|
ExpectedPath:=copy(Src,PathPos,CommentP-1-PathPos);
|
||||||
|
{$IFDEF VerboseFindDeclarationTests}
|
||||||
|
debugln(['TTestFindDeclaration.FindDeclarations searching "',Marker,'" at ',MainTool.CleanPosToStr(NameStartPos-1),' ExpectedPath=',ExpectedPath]);
|
||||||
|
{$ENDIF}
|
||||||
|
|
||||||
|
if (Marker='declaration') then begin
|
||||||
|
MainTool.CleanPosToCaret(IdentifierStartPos,CursorPos);
|
||||||
|
|
||||||
|
// test FindDeclaration
|
||||||
|
if not CodeToolBoss.FindDeclaration(CursorPos.Code,CursorPos.X,CursorPos.Y,
|
||||||
|
FoundCursorPos.Code,FoundCursorPos.X,FoundCursorPos.Y,FoundTopLine,
|
||||||
|
BlockTopLine,BlockBottomLine)
|
||||||
|
then begin
|
||||||
|
if ExpectedPath<>'' then begin
|
||||||
|
//if (CodeToolBoss.ErrorCode<>nil) then begin
|
||||||
|
//ErrorTool:=CodeToolBoss.GetCodeToolForSource(CodeToolBoss.ErrorCode);
|
||||||
|
//if ErrorTool<>MainTool then
|
||||||
|
// WriteSource(,ErrorTool);
|
||||||
|
WriteSource(IdentifierStartPos,MainTool);
|
||||||
|
Fail('find declaration failed at '+MainTool.CleanPosToStr(IdentifierStartPos,true)+': '+CodeToolBoss.ErrorMessage);
|
||||||
|
end;
|
||||||
|
continue;
|
||||||
|
end else begin
|
||||||
|
FoundTool:=CodeToolBoss.GetCodeToolForSource(FoundCursorPos.Code,true,true) as TFindDeclarationTool;
|
||||||
|
FoundPath:='';
|
||||||
|
FoundNode:=nil;
|
||||||
|
if (FoundCursorPos.Y=1) and (FoundCursorPos.X=1) then begin
|
||||||
|
// unit
|
||||||
|
FoundPath:=ExtractFileNameOnly(FoundCursorPos.Code.Filename);
|
||||||
|
end else begin
|
||||||
|
FoundTool.CaretToCleanPos(FoundCursorPos,FoundCleanPos);
|
||||||
|
if (FoundCleanPos>1) and (IsIdentChar[FoundTool.Src[FoundCleanPos-1]]) then
|
||||||
|
dec(FoundCleanPos);
|
||||||
|
FoundNode:=FoundTool.FindDeepestNodeAtPos(FoundCleanPos,true);
|
||||||
|
//debugln(['TTestFindDeclaration.FindDeclarations Found: ',FoundTool.CleanPosToStr(FoundNode.StartPos,true),' FoundNode=',FoundNode.DescAsString]);
|
||||||
|
FoundPath:=NodeAsPath(FoundTool,FoundNode);
|
||||||
|
end;
|
||||||
|
//debugln(['TTestFindDeclaration.FindDeclarations FoundPath=',FoundPath]);
|
||||||
|
if LowerCase(ExpectedPath)<>LowerCase(FoundPath) then begin
|
||||||
|
WriteSource(IdentifierStartPos,MainTool);
|
||||||
|
AssertEquals('find declaration wrong at '+MainTool.CleanPosToStr(IdentifierStartPos,true),LowerCase(ExpectedPath),LowerCase(FoundPath));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// test identifier completion
|
||||||
|
if (ExpectedPath<>'') then begin
|
||||||
|
for ExpexctedCompletionPart in ExpectedPath.Split(';') do begin
|
||||||
|
ExpexctedCompletion := ExpexctedCompletionPart;
|
||||||
|
StartOffs := 0;
|
||||||
|
if (ExpexctedCompletion <> '') and (ExpexctedCompletion[1] in ['+','-']) then begin
|
||||||
|
i := Pos('=', ExpexctedCompletion);
|
||||||
|
if i > 1 then begin
|
||||||
|
StartOffs := StrToIntDef(copy(ExpexctedCompletion, 1, i-1), 0);
|
||||||
|
Delete(ExpexctedCompletion, 1, i);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
StartOffs := 0;
|
||||||
|
end;
|
||||||
|
StartOffs := StartOffs + IdentifierStartPos;
|
||||||
|
MainTool.CleanPosToCaret(StartOffs,CursorPos);
|
||||||
|
|
||||||
|
if not CodeToolBoss.GatherIdentifiers(CursorPos.Code,CursorPos.X,CursorPos.Y)
|
||||||
|
then begin
|
||||||
|
if ExpexctedCompletion<>'' then begin
|
||||||
|
WriteSource(StartOffs,MainTool);
|
||||||
|
AssertEquals('GatherIdentifiers failed at '+MainTool.CleanPosToStr(StartOffs,true)+': '+CodeToolBoss.ErrorMessage,false,true);
|
||||||
|
end;
|
||||||
|
continue;
|
||||||
|
end else begin
|
||||||
|
for ExpexctedTermPart in ExpexctedCompletion.Split(',') do begin
|
||||||
|
ExpexctedTerm := ExpexctedTermPart;
|
||||||
|
ExpInvert := (ExpexctedTerm <> '') and (ExpexctedTerm[1] = '!');
|
||||||
|
if ExpInvert then
|
||||||
|
Delete(ExpexctedTerm, 1, 1);
|
||||||
|
i:=CodeToolBoss.IdentifierList.GetFilteredCount-1;
|
||||||
|
while i>=0 do begin
|
||||||
|
IdentItem:=CodeToolBoss.IdentifierList.FilteredItems[i];
|
||||||
|
//debugln(['TTestFindDeclaration.FindDeclarations ',IdentItem.Identifier]);
|
||||||
|
l:=length(IdentItem.Identifier);
|
||||||
|
if ((l=length(ExpexctedTerm)) or (ExpexctedTerm[length(ExpexctedTerm)-l]='.'))
|
||||||
|
and (CompareText(IdentItem.Identifier,RightStr(ExpexctedTerm,l))=0)
|
||||||
|
then break;
|
||||||
|
dec(i);
|
||||||
|
end;
|
||||||
|
if (i<0) and not ExpInvert then begin
|
||||||
|
WriteSource(StartOffs,MainTool);
|
||||||
|
AssertEquals('GatherIdentifiers misses "'+ExpexctedTerm+'" at '+MainTool.CleanPosToStr(StartOffs,true),true,i>=0);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if ExpInvert and (i>=0) then begin
|
||||||
|
WriteSource(StartOffs,MainTool);
|
||||||
|
AssertEquals('GatherIdentifiers should not have "'+ExpexctedTerm+'" at '+MainTool.CleanPosToStr(StartOffs,true),true,i>=0);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
end else if Marker='guesstype' then begin
|
||||||
|
ExpectedType:=copy(Src,PathPos,CommentP-1-PathPos);
|
||||||
|
{$IFDEF VerboseFindDeclarationTests}
|
||||||
|
debugln(['TTestFindDeclaration.FindDeclarations "',Marker,'" at ',Tool.CleanPosToStr(NameStartPos-1),' ExpectedType=',ExpectedType]);
|
||||||
|
{$ENDIF}
|
||||||
|
MainTool.CleanPosToCaret(IdentifierStartPos,CursorPos);
|
||||||
|
|
||||||
|
// test GuessTypeOfIdentifier
|
||||||
|
ListOfPFindContext:=nil;
|
||||||
|
try
|
||||||
|
if not CodeToolBoss.GuessTypeOfIdentifier(CursorPos.Code,CursorPos.X,CursorPos.Y,
|
||||||
|
ItsAKeyword, IsSubIdentifier, ExistingDefinition, ListOfPFindContext,
|
||||||
|
NewExprType, NewType)
|
||||||
|
then begin
|
||||||
|
if ExpectedType<>'' then
|
||||||
|
AssertEquals('GuessTypeOfIdentifier failed at '+MainTool.CleanPosToStr(IdentifierStartPos,true)+': '+CodeToolBoss.ErrorMessage,false,true);
|
||||||
|
continue;
|
||||||
|
end else begin
|
||||||
|
//debugln(['TTestFindDeclaration.FindDeclarations FoundPath=',FoundPath]);
|
||||||
|
if LowerCase(ExpectedType)<>LowerCase(NewType) then begin
|
||||||
|
WriteSource(IdentifierStartPos,MainTool);
|
||||||
|
AssertEquals('GuessTypeOfIdentifier wrong at '+MainTool.CleanPosToStr(IdentifierStartPos,true),LowerCase(ExpectedType),LowerCase(NewType));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FreeListOfPFindContext(ListOfPFindContext);
|
||||||
|
end;
|
||||||
|
|
||||||
end else begin
|
end else begin
|
||||||
FoundTool:=CodeToolBoss.GetCodeToolForSource(FoundCursorPos.Code,true,true) as TFindDeclarationTool;
|
WriteSource(IdentifierStartPos,MainTool);
|
||||||
FoundPath:='';
|
AssertEquals('Unknown marker at '+MainTool.CleanPosToStr(IdentifierStartPos,true),'declaration',Marker);
|
||||||
FoundNode:=nil;
|
continue;
|
||||||
if (FoundCursorPos.Y=1) and (FoundCursorPos.X=1) then begin
|
|
||||||
// unit
|
|
||||||
FoundPath:=ExtractFileNameOnly(FoundCursorPos.Code.Filename);
|
|
||||||
end else begin
|
|
||||||
FoundTool.CaretToCleanPos(FoundCursorPos,FoundCleanPos);
|
|
||||||
if (FoundCleanPos>1) and (IsIdentChar[FoundTool.Src[FoundCleanPos-1]]) then
|
|
||||||
dec(FoundCleanPos);
|
|
||||||
FoundNode:=FoundTool.FindDeepestNodeAtPos(FoundCleanPos,true);
|
|
||||||
//debugln(['TTestFindDeclaration.FindDeclarations Found: ',FoundTool.CleanPosToStr(FoundNode.StartPos,true),' FoundNode=',FoundNode.DescAsString]);
|
|
||||||
FoundPath:=NodeAsPath(FoundTool,FoundNode);
|
|
||||||
end;
|
|
||||||
//debugln(['TTestFindDeclaration.FindDeclarations FoundPath=',FoundPath]);
|
|
||||||
if LowerCase(ExpectedPath)<>LowerCase(FoundPath) then begin
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
AssertEquals('find declaration wrong at '+MainTool.CleanPosToStr(IdentifierStartPos,true),LowerCase(ExpectedPath),LowerCase(FoundPath));
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
|
until CommentP >= CommentEnd;
|
||||||
// test identifier completion
|
|
||||||
if (ExpectedPath<>'') then begin
|
|
||||||
if not CodeToolBoss.GatherIdentifiers(CursorPos.Code,CursorPos.X,CursorPos.Y)
|
|
||||||
then begin
|
|
||||||
if ExpectedPath<>'' then begin
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
AssertEquals('GatherIdentifiers failed at '+MainTool.CleanPosToStr(IdentifierStartPos,true)+': '+CodeToolBoss.ErrorMessage,false,true);
|
|
||||||
end;
|
|
||||||
continue;
|
|
||||||
end else begin
|
|
||||||
i:=CodeToolBoss.IdentifierList.GetFilteredCount-1;
|
|
||||||
while i>=0 do begin
|
|
||||||
IdentItem:=CodeToolBoss.IdentifierList.FilteredItems[i];
|
|
||||||
//debugln(['TTestFindDeclaration.FindDeclarations ',IdentItem.Identifier]);
|
|
||||||
l:=length(IdentItem.Identifier);
|
|
||||||
if ((l=length(ExpectedPath)) or (ExpectedPath[length(ExpectedPath)-l]='.'))
|
|
||||||
and (CompareText(IdentItem.Identifier,RightStr(ExpectedPath,l))=0)
|
|
||||||
then break;
|
|
||||||
dec(i);
|
|
||||||
end;
|
|
||||||
if i<0 then begin
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
AssertEquals('GatherIdentifiers misses "'+ExpectedPath+'" at '+MainTool.CleanPosToStr(IdentifierStartPos,true),true,i>=0);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end else if Marker='guesstype' then begin
|
|
||||||
ExpectedType:=copy(Src,PathPos,CommentP-1-PathPos);
|
|
||||||
{$IFDEF VerboseFindDeclarationTests}
|
|
||||||
debugln(['TTestFindDeclaration.FindDeclarations "',Marker,'" at ',Tool.CleanPosToStr(NameStartPos-1),' ExpectedType=',ExpectedType]);
|
|
||||||
{$ENDIF}
|
|
||||||
MainTool.CleanPosToCaret(IdentifierStartPos,CursorPos);
|
|
||||||
|
|
||||||
// test GuessTypeOfIdentifier
|
|
||||||
ListOfPFindContext:=nil;
|
|
||||||
try
|
|
||||||
if not CodeToolBoss.GuessTypeOfIdentifier(CursorPos.Code,CursorPos.X,CursorPos.Y,
|
|
||||||
ItsAKeyword, IsSubIdentifier, ExistingDefinition, ListOfPFindContext,
|
|
||||||
NewExprType, NewType)
|
|
||||||
then begin
|
|
||||||
if ExpectedType<>'' then
|
|
||||||
AssertEquals('GuessTypeOfIdentifier failed at '+MainTool.CleanPosToStr(IdentifierStartPos,true)+': '+CodeToolBoss.ErrorMessage,false,true);
|
|
||||||
continue;
|
|
||||||
end else begin
|
|
||||||
//debugln(['TTestFindDeclaration.FindDeclarations FoundPath=',FoundPath]);
|
|
||||||
if LowerCase(ExpectedType)<>LowerCase(NewType) then begin
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
AssertEquals('GuessTypeOfIdentifier wrong at '+MainTool.CleanPosToStr(IdentifierStartPos,true),LowerCase(ExpectedType),LowerCase(NewType));
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
finally
|
|
||||||
FreeListOfPFindContext(ListOfPFindContext);
|
|
||||||
end;
|
|
||||||
|
|
||||||
end else begin
|
|
||||||
WriteSource(IdentifierStartPos,MainTool);
|
|
||||||
AssertEquals('Unknown marker at '+MainTool.CleanPosToStr(IdentifierStartPos,true),'declaration',Marker);
|
|
||||||
continue;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
CheckReferenceMarkers;
|
CheckReferenceMarkers;
|
||||||
end;
|
end;
|
||||||
|
Loading…
Reference in New Issue
Block a user