findwriteln.pas 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. unit FindWriteln;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils;
  6. type
  7. TFindWritelnLog = procedure(EventType : TEventType; const Msg: string) of object;
  8. function FindWritelnInDirectory(Dir: string; Recurse: boolean; const Log: TFindWritelnLog): integer;
  9. implementation
  10. function ReadNextToken(const Src: string; var SrcP: PChar; var Line: integer): string;
  11. var
  12. p, TokenStart: PChar;
  13. begin
  14. p:=SrcP;
  15. while p^ in [' ',#9] do inc(p);
  16. repeat
  17. case p^ of
  18. #0:
  19. if p-PChar(Src)=length(Src) then begin
  20. SrcP:=p;
  21. exit('');
  22. end
  23. else
  24. inc(p);
  25. #10,#13:
  26. begin
  27. inc(Line);
  28. if (p[1] in [#10,#13]) and (p^<>p[1]) then
  29. inc(p,2)
  30. else
  31. inc(p);
  32. end;
  33. ' ',#9:
  34. inc(p);
  35. else
  36. break;
  37. end;
  38. until false;
  39. TokenStart:=p;
  40. case p^ of
  41. 'a'..'z','A'..'Z','_':
  42. while p^ in ['a'..'z','A'..'Z','_','0'..'9'] do inc(p);
  43. '0'..'9':
  44. while p^ in ['0'..'9'] do inc(p);
  45. '''':
  46. begin
  47. inc(p);
  48. repeat
  49. case p^ of
  50. #0,#10,#13: break;
  51. '''':
  52. begin
  53. inc(p);
  54. break;
  55. end;
  56. end;
  57. inc(p);
  58. until false;
  59. end;
  60. '/':
  61. if p[1]='/' then begin
  62. inc(p,2);
  63. while not (p^ in [#0,#10,#13]) do inc(p);
  64. end else
  65. inc(p);
  66. '{':
  67. begin
  68. inc(p);
  69. repeat
  70. case p^ of
  71. #0:
  72. if p-PChar(Src)=length(Src) then begin
  73. SrcP:=p;
  74. exit('');
  75. end;
  76. #10,#13:
  77. begin
  78. inc(Line);
  79. if (p[1] in [#10,#13]) and (p^<>p[1]) then
  80. inc(p);
  81. end;
  82. '}': break;
  83. end;
  84. inc(p);
  85. until false;
  86. inc(p);
  87. end;
  88. '(':
  89. if p[1]='*' then begin
  90. inc(p,2);
  91. repeat
  92. case p^ of
  93. #0:
  94. if p-PChar(Src)=length(Src) then begin
  95. SrcP:=p;
  96. exit('');
  97. end;
  98. #10,#13:
  99. begin
  100. inc(Line);
  101. if (p[1] in [#10,#13]) and (p^<>p[1]) then
  102. inc(p);
  103. end;
  104. '*':
  105. if p[1]=')' then break;
  106. end;
  107. inc(p);
  108. until false;
  109. inc(p,2);
  110. end else
  111. inc(p);
  112. else
  113. inc(p);
  114. end;
  115. SetLength(Result,p-TokenStart);
  116. Move(TokenStart^,Result[1],length(Result));
  117. SrcP:=P;
  118. end;
  119. procedure GetLineStartEndAtPosition(const Source:string; Position:integer;
  120. out LineStart,LineEnd:integer);
  121. begin
  122. if Position<1 then begin
  123. LineStart:=0;
  124. LineEnd:=0;
  125. exit;
  126. end;
  127. if Position>length(Source)+1 then begin
  128. LineStart:=length(Source)+1;
  129. LineEnd:=LineStart;
  130. exit;
  131. end;
  132. LineStart:=Position;
  133. while (LineStart>1) and (not (Source[LineStart-1] in [#10,#13])) do
  134. dec(LineStart);
  135. LineEnd:=Position;
  136. while (LineEnd<=length(Source)) and (not (Source[LineEnd] in [#10,#13])) do
  137. inc(LineEnd);
  138. end;
  139. function GetLineInSrc(const Source: string; Position: integer): string;
  140. var
  141. LineStart, LineEnd: integer;
  142. begin
  143. GetLineStartEndAtPosition(Source,Position,LineStart,LineEnd);
  144. Result:=copy(Source,LineStart,LineEnd-LineStart);
  145. end;
  146. function CheckFile(Filename: string; const Log: TFindWritelnLog): integer;
  147. var
  148. Token, LastToken, Src: String;
  149. ms: TMemoryStream;
  150. p: PChar;
  151. Line, LastIFDEF, AllowWriteln: Integer;
  152. Lvl, VerboseLvl: integer;
  153. begin
  154. Result:=0;
  155. ms:=TMemoryStream.Create;
  156. try
  157. ms.LoadFromFile(Filename);
  158. if ms.Size=0 then exit;
  159. Src:='';
  160. SetLength(Src,ms.Size);
  161. Move(ms.Memory^,Src[1],length(Src));
  162. p:=PChar(Src);
  163. AllowWriteln:=0;
  164. Line:=1;
  165. LastIFDEF:=-1;
  166. Token:='';
  167. Lvl:=0;
  168. VerboseLvl:=-1;
  169. repeat
  170. LastToken:=Token;
  171. Token:=ReadNextToken(Src,p,Line);
  172. if Token='' then break;
  173. if Token[1]='{' then begin
  174. Token:=lowercase(Token);
  175. if Token='{allowwriteln}' then begin
  176. if AllowWriteln>0 then begin
  177. inc(Result);
  178. Log(etError,Filename+'('+IntToStr(Line)+'): writeln already allowed at '+IntToStr(AllowWriteln)+': '+GetLineInSrc(Src,p-PChar(Src)+1));
  179. end;
  180. AllowWriteln:=Line;
  181. end
  182. else if Token='{allowwriteln-}' then begin
  183. if AllowWriteln<1 then begin
  184. inc(Result);
  185. Log(etError,Filename+'('+IntToStr(Line)+'): writeln was not allowed: '+GetLineInSrc(Src,p-PChar(Src)+1));
  186. end;
  187. AllowWriteln:=0;
  188. end
  189. else if SameText(LeftStr(Token,4),'{$if') then begin
  190. inc(Lvl);
  191. LastIFDEF:=Line;
  192. if SameText(LeftStr(Token,15),'{$ifdef Verbose') then begin
  193. if VerboseLvl<0 then VerboseLvl:=Lvl;
  194. end;
  195. end else if SameText(LeftStr(Token,6),'{$else') then begin
  196. if Lvl=VerboseLvl then
  197. VerboseLvl:=-1;
  198. LastIFDEF:=Line;
  199. end else if SameText(LeftStr(Token,7),'{$endif') then begin
  200. if Lvl=VerboseLvl then begin
  201. VerboseLvl:=-1;
  202. end;
  203. dec(Lvl);
  204. end;
  205. end
  206. else begin
  207. if (CompareText(Token,'str')=0) and (LastToken<>'.') then begin
  208. if byte(Line-LastIFDEF) in [0,1] then begin
  209. // ignore writeln just behind IFDEF
  210. LastIFDEF:=Line;
  211. end;
  212. end;
  213. if (CompareText(Token,'writeln')=0)
  214. and (LastToken<>'.')
  215. and (LastToken<>':=')
  216. and (LastToken<>'=')
  217. and (LastToken<>'+')
  218. and not SameText(LastToken,'function')
  219. and not SameText(LastToken,'procedure') then begin
  220. if Lvl=VerboseLvl then begin
  221. // ignore writeln inside $IFDEF VerboseX
  222. end else if byte(Line-LastIFDEF) in [0,1] then begin
  223. // ignore writeln just behind IFDEF
  224. LastIFDEF:=Line;
  225. end else if AllowWriteln<1 then begin
  226. inc(Result);
  227. Log(etError,Filename+'('+IntToStr(Line)+'): '+GetLineInSrc(Src,p-PChar(Src)+1));
  228. end;
  229. end;
  230. end;
  231. until false;
  232. finally
  233. ms.Free;
  234. end;
  235. end;
  236. function FindWritelnInDirectory(Dir: string; Recurse: boolean; const Log: TFindWritelnLog): integer;
  237. var
  238. Info: TRawByteSearchRec;
  239. Ext: String;
  240. begin
  241. Result:=0;
  242. Dir:=IncludeTrailingPathDelimiter(Dir);
  243. if FindFirst(Dir+AllFilesMask,faAnyFile,Info)=0 then begin
  244. repeat
  245. if (Info.Name='') or (Info.Name='.') or (Info.Name='..') then continue;
  246. if (Info.Attr and faDirectory)>0 then begin
  247. if Recurse then
  248. Result+=FindWritelnInDirectory(Dir+Info.Name,true,Log);
  249. end
  250. else begin
  251. Ext:=lowercase(ExtractFileExt(Info.Name));
  252. case Ext of
  253. '.p','.pp','.pas','.inc': Result+=CheckFile(Dir+Info.Name,Log);
  254. end;
  255. end;
  256. until FindNext(Info)<>0;
  257. FindClose(Info);
  258. end;
  259. end;
  260. end.