tcsrcmap.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2014 by Michael Van Canneyt
  4. Unit tests for Pascal-to-Javascript source map.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************
  11. Examples:
  12. ./testpas2js --suite=TTestSrcMap.TestEmptyProgram
  13. }
  14. unit TCSrcMap;
  15. {$mode objfpc}{$H+}
  16. interface
  17. uses
  18. Classes, SysUtils, fpcunit, testregistry,
  19. jstree, jswriter, JSSrcMap,
  20. FPPas2Js, FPPJsSrcMap,
  21. tcmodules, PasResolveEval;
  22. type
  23. { TCustomTestSrcMap }
  24. TCustomTestSrcMap = class(TCustomTestModule)
  25. private
  26. FJS_Writer: TJSWriter;
  27. FPas2JSMapper: TPas2JSMapper;
  28. FSrcMap: TPas2JSSrcMap;
  29. protected
  30. procedure SetUp; override;
  31. procedure TearDown; override;
  32. function ConvertJSModuleToString(El: TJSElement): string; override;
  33. procedure CheckSrcMap(const aTitle: string; const JSLines: array of string); virtual;
  34. procedure WriteSrcMapLine(GeneratedLine: integer);
  35. public
  36. property Pas2JSMapper: TPas2JSMapper read FPas2JSMapper; // fills SrcMap
  37. property SrcMap: TPas2JSSrcMap read FSrcMap; // map container
  38. property JS_Writer: TJSWriter read FJS_Writer; // JS element to text
  39. end;
  40. { TTestSrcMap }
  41. TTestSrcMap = class(TCustomTestSrcMap)
  42. published
  43. procedure TestEmptyProgram;
  44. procedure TestEmptyUnit;
  45. procedure TestIf;
  46. procedure TestIfBegin;
  47. procedure TestForConstRange;
  48. procedure TestFunction;
  49. procedure TestExternalObjCall;
  50. procedure TestBracketAccessor;
  51. end;
  52. implementation
  53. { TCustomTestSrcMap }
  54. procedure TCustomTestSrcMap.SetUp;
  55. begin
  56. FSrcMap:=TPas2JSSrcMap.Create('test1.js.map');
  57. FPas2JSMapper:=TPas2JSMapper.Create(4096);
  58. FPas2JSMapper.SrcMap:=SrcMap;
  59. SrcMap.Release;// release the refcount from the Create
  60. //SrcMap.SourceRoot:='';
  61. //SrcMap.LocalFilename:='';
  62. fJS_Writer:=TJSWriter.Create(Pas2JSMapper);
  63. JS_Writer.IndentSize:=2;
  64. inherited SetUp;
  65. end;
  66. procedure TCustomTestSrcMap.TearDown;
  67. begin
  68. // Note: SrcMap is freed by freeing Pas2JSMapper
  69. FreeAndNil(FJS_Writer);
  70. FreeAndNil(FPas2JSMapper);
  71. inherited TearDown;
  72. end;
  73. function TCustomTestSrcMap.ConvertJSModuleToString(El: TJSElement): string;
  74. begin
  75. writeln('TCustomTestSrcMap.JSToStr ',GetObjName(El));
  76. JS_Writer.WriteJS(El);
  77. Result:=Pas2JSMapper.AsString;
  78. end;
  79. procedure TCustomTestSrcMap.CheckSrcMap(const aTitle: string;
  80. const JSLines: array of string);
  81. type
  82. TMarker = record
  83. Name: string;
  84. PasLine: integer; // 1-based
  85. PasColMin,PasColMax: integer; // 0-based
  86. end;
  87. PMarker = ^TMarker;
  88. var
  89. Markers: array of TMarker;
  90. PasSrc: TStringList;
  91. function IndexOfMarker(const aName: String): integer;
  92. var
  93. i: Integer;
  94. begin
  95. for i:=0 to length(Markers)-1 do
  96. if CompareText(Markers[i].Name,aName)=0 then
  97. exit(i);
  98. Result:=-1;
  99. end;
  100. procedure AddMarker(const aName: String; PasLine, PasColMin, PasColMax: integer);
  101. var
  102. i, l: Integer;
  103. p: PMarker;
  104. begin
  105. if IndexOfMarker(aName)>0 then
  106. begin
  107. writeln('AddMarker duplicate marker "',aName,'"');
  108. for i:=1 to PasLine do
  109. writeln(PasSrc[i-1]);
  110. Fail('duplicate marker "'+aName+'"');
  111. end;
  112. l:=length(Markers);
  113. SetLength(Markers,l+1);
  114. p:=@Markers[l];
  115. p^.Name:=aName;
  116. p^.PasLine:=PasLine;
  117. p^.PasColMin:=PasColMin;
  118. p^.PasColMax:=PasColMax;
  119. end;
  120. procedure JSMarkerError(Line, Col: integer; Msg: string);
  121. var
  122. i: Integer;
  123. begin
  124. for i:=0 to Line-1 do
  125. writeln(JSSource[i]);
  126. for i:=1 to Col do write('-');
  127. writeln('^');
  128. Fail(Msg+' at '+IntToStr(Line)+','+IntToStr(Col));
  129. end;
  130. var
  131. i, j, ColMin, ColMax: integer;
  132. Line, aName, SegFile, ActLine: String;
  133. p, StartP, ActP: PChar;
  134. m: PMarker;
  135. aSeg: TSourceMapSegment;
  136. begin
  137. {$IFDEF VerbosePas2JS}
  138. writeln('TCustomTestSrcMap.CheckSrcMap ',aTitle);
  139. {for i:=0 to SrcMap.Count-1 do
  140. begin
  141. write('TCustomTestSrcMap.CheckSrcMap i=',i,' Gen=',
  142. SrcMap[i].GeneratedLine,',',SrcMap[i].GeneratedColumn);
  143. write(' Src=');
  144. if SrcMap[i].SrcFileIndex>0 then
  145. write(SrcMap.SourceFiles[SrcMap[i].SrcFileIndex],',');
  146. writeln(SrcMap[i].SrcLine,',',SrcMap[i].SrcColumn);
  147. end;}
  148. for i:=1 to JSSource.Count do
  149. WriteSrcMapLine(i);
  150. WriteSources(Filename,1,1);
  151. writeln('......012345678901234567890123456789012345678901234567890123456789');
  152. {$ENDIF}
  153. if Low(JSLines)<>0 then
  154. {%H-}Fail('inconsistency');
  155. AssertEquals('expected JavaScript lines',High(JSLines)+1,JSSource.Count);
  156. // collect markers in Pascal
  157. PasSrc:=TStringList.Create;
  158. try
  159. PasSrc.Text:=ResolverEngine.Source;
  160. for i:=1 to PasSrc.Count do
  161. begin
  162. Line:=PasSrc[i-1];
  163. p:=PChar(Line);
  164. repeat
  165. case p^ of
  166. #0: break;
  167. '(':
  168. if (p[1]='*') and (p[2] in ['a'..'z','A'..'Z','_']) then
  169. begin
  170. ColMin:=p-PChar(Line);
  171. inc(p,2);
  172. StartP:=p;
  173. while p^ in ['a'..'z','A'..'Z','0'..'9','_'] do inc(p);
  174. aName:=copy(Line,StartP-PChar(Line)+1,p-StartP);
  175. if (p^<>'*') or (p[1]<>')') then
  176. begin
  177. for j:=1 to i do
  178. writeln(PasSrc[j-1]);
  179. Fail('missing closing bracket of Pascal marker at '+IntToStr(i)+','+IntToStr(p-PChar(Line)));
  180. end;
  181. inc(p,2);
  182. ColMax:=p-PChar(Line);
  183. AddMarker(aName,i,ColMin,ColMax);
  184. continue;
  185. end;
  186. end;
  187. inc(p);
  188. until false;
  189. end;
  190. // check JavaScript markers
  191. for i:=1 to JSSource.Count do
  192. begin
  193. ActLine:=JSSource[i-1];
  194. if i>High(JSLines)+1 then
  195. begin
  196. writeln('TCustomTestSrcMap.CheckSrcMap unexpected JS line ',i,': ',ActLine);
  197. Fail('created JS has more lines than expected JS');
  198. end;
  199. ActP:=PChar(ActLine);
  200. Line:=JSLines[i-1];
  201. p:=PChar(Line);
  202. repeat
  203. case p^ of
  204. #0: break;
  205. '(':
  206. if (p[1]='*') and (p[2] in ['a'..'z','A'..'Z','_']) then
  207. begin
  208. ColMin:=ActP-PChar(ActLine);
  209. inc(p,2);
  210. StartP:=p;
  211. while p^ in ['a'..'z','A'..'Z','0'..'9','_'] do inc(p);
  212. aName:=copy(Line,StartP-PChar(Line)+1,p-StartP);
  213. if (p^<>'*') or (p[1]<>')') then
  214. begin
  215. for j:=1 to i do
  216. writeln(JSSource[j-1]);
  217. Fail('missing closing bracket of JS marker at '+IntToStr(i)+','+IntToStr(ColMin));
  218. end;
  219. inc(p,2);
  220. j:=IndexOfMarker(aName);
  221. if j<0 then
  222. JSMarkerError(i,ColMin,'JS marker "'+aName+'" not found in Pascal');
  223. m:=@Markers[j];
  224. j:=SrcMap.IndexOfSegmentAt(i,ColMin);
  225. if j<0 then
  226. JSMarkerError(i,ColMin,'JS marker "'+aName+'" has no segment in SrcMap');
  227. aSeg:=SrcMap[j];
  228. SegFile:=SrcMap.SourceFiles[aSeg.SrcFileIndex];
  229. if SegFile<>Filename then
  230. JSMarkerError(i,ColMin,'JS marker "'+aName+'" maps to file "'+SegFile+'" instead of "'+Filename+'"');
  231. if aSeg.SrcLine<>m^.PasLine then
  232. JSMarkerError(i,ColMin,'JS marker "'+aName+'" maps to Pascal line "'+IntToStr(aSeg.SrcLine)+'" instead of "'+IntToStr(m^.PasLine)+'"');
  233. if (aSeg.SrcColumn<m^.PasColMin) or (aSeg.SrcColumn>m^.PasColMax) then
  234. JSMarkerError(i,ColMin,'JS marker "'+aName+'" maps to Pascal col "'+IntToStr(aSeg.SrcColumn)+'" instead of "'+IntToStr(m^.PasColMin)+'-'+IntToStr(m^.PasColMax)+'"');
  235. continue;
  236. end;
  237. end;
  238. if p^<>ActP^ then
  239. begin
  240. writeln('JavaScript: ');
  241. for j:=0 to i-1 do
  242. writeln(JSSource[j]);
  243. for j:=1 to P-PChar(Line) do write('-');
  244. writeln('^');
  245. writeln('Expected JS:<',Line,'>');
  246. AssertEquals('Expected JavaScript differs',p^,ActP^);
  247. end;
  248. inc(p);
  249. inc(ActP);
  250. until false;
  251. end;
  252. finally
  253. PasSrc.Free;
  254. end;
  255. end;
  256. procedure TCustomTestSrcMap.WriteSrcMapLine(GeneratedLine: integer);
  257. var
  258. JS, Origins: String;
  259. begin
  260. JS:=JSSource[GeneratedLine-1];
  261. DebugSrcMapLine(GeneratedLine,JS,SrcMap,Origins);
  262. writeln(JS);
  263. writeln(Origins);
  264. end;
  265. { TTestSrcMap }
  266. procedure TTestSrcMap.TestEmptyProgram;
  267. begin
  268. StartProgram(false);
  269. Add('(*b*)begin');
  270. ConvertProgram;
  271. CheckSrcMap('TestEmptyProgram',[
  272. 'rtl.module("program", [], function () {',
  273. ' var $mod = this;',
  274. '(*b*) $mod.$main = function () {',
  275. ' };',
  276. '});']);
  277. end;
  278. procedure TTestSrcMap.TestEmptyUnit;
  279. begin
  280. StartUnit(false);
  281. Add([
  282. 'interface',
  283. 'implementation'
  284. ]);
  285. ConvertUnit;
  286. CheckSrcMap('TestEmptyUnit',[
  287. 'rtl.module("Test1", [], function () {',
  288. ' var $mod = this;',
  289. '});']);
  290. end;
  291. procedure TTestSrcMap.TestIf;
  292. begin
  293. StartProgram(false);
  294. Add([
  295. 'var (*i*)i: longint;',
  296. 'begin',
  297. ' if true then',
  298. ' (*a*)i:=(*b*)1234 (*c*)+ (*d*)2222',
  299. ' else',
  300. ' i:=3456;']);
  301. ConvertProgram;
  302. CheckSrcMap('TestIf',[
  303. 'rtl.module("program", [], function () {',
  304. ' var $mod = this;',
  305. ' this.(*i*)i = 0;',
  306. ' $mod.$main = function () {',
  307. ' if (true) {',
  308. ' (*a*)$mod.i = (*b*)1234 (*c*)+ (*d*)2222}',
  309. ' else $mod.i = 3456;',
  310. ' };',
  311. '});']);
  312. end;
  313. procedure TTestSrcMap.TestIfBegin;
  314. begin
  315. StartProgram(false);
  316. Add([
  317. 'var',
  318. ' (*E*)E, (*P*)P: String;',
  319. 'begin',
  320. ' (*E2*)E:=(*bla*)''bla'';',
  321. ' (*if1*)if E=P then',
  322. ' begin',
  323. ' (*then*)E:=''active'';',
  324. ' end',
  325. ' else',
  326. ' begin',
  327. ' (*else*)E:=''inactive'';',
  328. ' end;']);
  329. ConvertProgram;
  330. CheckSrcMap('TestIfBegin',[
  331. 'rtl.module("program", [], function () {',
  332. ' var $mod = this;',
  333. ' this.(*E*)E = "";',
  334. ' this.(*P*)P = "";',
  335. ' $mod.$main = function () {',
  336. '(*E2*) $mod.E = (*bla*)"bla";(*bla*)',
  337. ' (*if1*)if ($mod.E === $mod.P) {(*if1*)',
  338. '(*then*) $mod.E = "active";',
  339. ' } else {',
  340. '(*else*) $mod.E = "inactive";',
  341. ' };',
  342. ' };',
  343. '});']);
  344. end;
  345. procedure TTestSrcMap.TestForConstRange;
  346. begin
  347. StartProgram(false);
  348. Add([
  349. 'var Runner, i: longint;',
  350. 'begin',
  351. ' (*for*)for (*r*)Runner := (*start*)1000 to (*end*)3000 do',
  352. ' (*inc*)inc(i);']);
  353. ConvertProgram;
  354. CheckSrcMap('TestForConstRange',[
  355. 'rtl.module("program", [], function () {',
  356. ' var $mod = this;',
  357. ' this.Runner = 0;',
  358. ' this.i = 0;',
  359. ' $mod.$main = function () {',
  360. '(*for*) for ((*r*)$mod.Runner = (*start*)1000; (*r*)$mod.Runner (*end*)<= 3000; (*r*)$mod.Runner++) $mod.i (*inc*)+= 1;',
  361. ' };',
  362. '});'
  363. ]);
  364. end;
  365. procedure TTestSrcMap.TestFunction;
  366. begin
  367. StartProgram(false);
  368. Add([
  369. 'function DoIt(i: longint): longint; forward;',
  370. 'const p = 3;',
  371. 'function (*ResultInit*)DoIt(*DoIt*)(i: longint): longint;',
  372. 'var Runner, j: longint;',
  373. 'begin',
  374. ' j:=0;',
  375. ' (*for*)for (*r*)Runner := (*start*)p to (*end*)j do',
  376. ' (*inc*)inc(j);',
  377. ' Result:=j;',
  378. 'end;',
  379. 'begin',
  380. ' (*CallDoIt*)DoIt(2);']);
  381. ConvertProgram;
  382. CheckSrcMap('TestFunction',[
  383. 'rtl.module("program", [], function () {',
  384. ' var $mod = this;',
  385. ' this.p = 3;',
  386. '(*DoIt*) this.DoIt = function (i) {',
  387. '(*ResultInit*) var Result = 0;',
  388. ' var Runner = 0;',
  389. ' var j = 0;',
  390. ' j = 0;',
  391. ' for (var $l = 3, $end = j; $l <= $end; $l++) {',
  392. ' Runner = $l;',
  393. ' j += 1;',
  394. ' };',
  395. ' Result = j;',
  396. ' return Result;',
  397. ' };',
  398. ' $mod.$main = function () {',
  399. '(*CallDoIt*) $mod.DoIt(2);',
  400. ' };',
  401. '});'
  402. ]);
  403. end;
  404. procedure TTestSrcMap.TestExternalObjCall;
  405. begin
  406. StartProgram(false);
  407. Add([
  408. '{$modeswitch externalclass}',
  409. 'type',
  410. ' TJSConsole = class external name ''Console''',
  411. ' Public',
  412. ' procedure log(Obj1 : JSValue); varargs;',
  413. ' end;',
  414. 'var console : TJSConsole; external name ''window.console'';',
  415. ' xhrstatus: longint;',
  416. 'begin',
  417. ' (*w*)console(*log*).log (''state'');',
  418. ' if xhrstatus=200 then',
  419. ' begin',
  420. ' xhrstatus:=3;',
  421. ' xhrstatus:=4;',
  422. ' end;']);
  423. ConvertProgram;
  424. CheckSrcMap('TestExternalObjCall',[
  425. 'rtl.module("program", [], function () {',
  426. ' var $mod = this;',
  427. ' this.xhrstatus = 0;',
  428. ' $mod.$main = function () {',
  429. ' (*w*)window.console(*log*).log("state");',
  430. ' if ($mod.xhrstatus === 200) {',
  431. ' $mod.xhrstatus = 3;',
  432. ' $mod.xhrstatus = 4;',
  433. ' };',
  434. ' };',
  435. '});'
  436. ]);
  437. end;
  438. procedure TTestSrcMap.TestBracketAccessor;
  439. begin
  440. StartProgram(false);
  441. Add([
  442. '{$modeswitch externalclass}',
  443. 'type',
  444. ' TJSObject = class external name ''Object''',
  445. ' private',
  446. ' function GetProperties(Name: String): JSValue; external name ''[]'';',
  447. ' Public',
  448. ' property Properties[Name: string]: JSValue read GetProperties;',
  449. ' end;',
  450. 'var Obj : TJSObject;',
  451. ' j: JSValue;',
  452. 'begin',
  453. ' (*j*)j:=(*Obj*)Obj.Properties[(*bracket*)''state''];',
  454. ' ']);
  455. ConvertProgram;
  456. CheckSrcMap('TestExternalObjCall',[
  457. 'rtl.module("program", [], function () {',
  458. ' var $mod = this;',
  459. ' this.Obj = null;',
  460. ' this.j = undefined;',
  461. ' $mod.$main = function () {',
  462. '(*j*) $mod.j = (*Obj*)$mod.Obj(*bracket*)["state"];',
  463. ' };',
  464. '});']);
  465. end;
  466. Initialization
  467. RegisterTests([TTestSrcMap]);
  468. end.