1
0

cpp-raw-string-preparse-1505.test.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { beforeAll, describe, expect, it } from 'vitest';
  2. import { extractFromSource } from '../src/extraction';
  3. import { getParser, initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
  4. import {
  5. blankCppAnnotationMacroCalls,
  6. blankCppInlineAnnotationMacros,
  7. blankCStatementMacroCalls,
  8. blankCTypeKeywordArgs,
  9. blankCFileScopePrefixedDeclMacros,
  10. blankCParameterizedAnnotationMacros,
  11. blankCDesignatedMacroArgs,
  12. cExtractor,
  13. cppExtractor,
  14. } from '../src/extraction/languages/c-cpp';
  15. // The original parses cleanly; #1505 is preParse corrupting its short delimiter,
  16. // distinct from the vendored grammar's 16-character delimiter error (#1522).
  17. function scaffoldSource(delimiter = 'GEN'): string {
  18. return `#include <string>
  19. namespace {
  20. const char* kTpl = R"${delimiter}(
  21. DECLARE_THING(
  22. struct Ignored { int v; };
  23. int nested_fn() { return 1; }
  24. )${delimiter}";
  25. }
  26. int create_scaffold(int x) {
  27. return x;
  28. }
  29. int helper_after(int y) {
  30. return y + 1;
  31. }
  32. `;
  33. }
  34. const annotationBlankers = [
  35. { name: 'line-leading annotations', blank: blankCppAnnotationMacroCalls },
  36. { name: 'inline annotations', blank: blankCppInlineAnnotationMacros },
  37. ];
  38. describe('C/C++ raw strings survive preParse (#1505)', () => {
  39. beforeAll(async () => {
  40. await initGrammars();
  41. await loadGrammarsForLanguages(['cpp']);
  42. });
  43. it('indexes both functions after the anonymous namespace without introducing a parse error', () => {
  44. const source = scaffoldSource();
  45. const rewritten = cppExtractor.preParse!(source, 'scaffold.cpp');
  46. for (const text of [source, rewritten]) {
  47. const tree = getParser('cpp')!.parse(text)!;
  48. try {
  49. expect(tree.rootNode.hasError).toBe(false);
  50. } finally {
  51. tree.delete();
  52. }
  53. }
  54. const result = extractFromSource('scaffold.cpp', source);
  55. expect(result.nodes.filter((node) => node.kind === 'function').map((node) => node.name))
  56. .toEqual(['create_scaffold', 'helper_after']);
  57. expect(result.nodes.some((node) => node.name === 'Ignored')).toBe(false);
  58. expect(result.errors).toEqual([]);
  59. expect(rewritten).toBe(source);
  60. });
  61. it('keeps the raw-string terminator at its original offset', () => {
  62. const source = scaffoldSource('TAG');
  63. const closer = source.indexOf(')TAG"');
  64. const blanked = blankCppAnnotationMacroCalls(source);
  65. expect(blanked.slice(closer, closer + 5)).toBe(')TAG"');
  66. expect(blanked).toBe(source);
  67. });
  68. describe.each(annotationBlankers)('$name', ({ blank }) => {
  69. it.each(['R', 'LR', 'u8R', 'uR', 'UR'])('leaves %s raw-string contents untouched', (prefix) => {
  70. const source = `const auto* text = ${prefix}"TAG(
  71. DECLARE_THING(
  72. "quoted ) text" and 'characters' and a backslash \\
  73. )OTHER"
  74. value UPARAM(ref) UE_DEPRECATED(
  75. )TAG";
  76. `;
  77. expect(blank(source)).toBe(source);
  78. });
  79. it.each(['', 'FIFTEEN_CHARS__', 'SIXTEEN_CHARS___'])('protects delimiter %j with CRLF', (delimiter) => {
  80. const source = `const char* text = R"${delimiter}(\r\nUE_DEPRECATED(\r\n)${delimiter}";\r\n`;
  81. expect(blank(source)).toBe(source);
  82. });
  83. it('leaves an unterminated raw string untouched', () => {
  84. const source = 'const char* text = R"TAG(\nUE_DEPRECATED(1)\nint example;';
  85. expect(blank(source)).toBe(source);
  86. });
  87. });
  88. it.each([
  89. { name: 'line-leading', blank: blankCppAnnotationMacroCalls, head: '', macro: 'ANNOTATE', tail: '\nint helper_after() { return 1; }\n' },
  90. { name: 'inline', blank: blankCppInlineAnnotationMacros, head: 'using Alias ', macro: 'UE_DEPRECATED', tail: ' = int;\n' },
  91. { name: 'C parameterized', blank: blankCParameterizedAnnotationMacros, head: 'static void ', macro: '__section', tail: ' helper_after(void) {}\n' },
  92. { name: 'C iterator', blank: blankCStatementMacroCalls, head: 'void iterate() {\n ', macro: 'for_each_item', tail: ' {\n visit();\n }\n}\n' },
  93. ])('balances a genuine $name macro containing a raw-string argument', ({ blank, head, macro, tail }) => {
  94. const annotation = `${macro}(R"TAG(" ) unbalanced ( " \\
  95. UE_DEPRECATED(
  96. )TAG")`;
  97. expect(blank(head + annotation + tail))
  98. .toBe(head + annotation.replace(/[^\r\n]/g, ' ') + tail);
  99. });
  100. it('balances a C declaration macro with a raw-string argument', () => {
  101. const macro = 'static DECLARE_THING(R"TAG(" ) unbalanced ( ")TAG");';
  102. const tail = '\nint helper_after(void) {}\n';
  103. expect(blankCFileScopePrefixedDeclMacros(macro + tail))
  104. .toBe(' '.repeat(macro.length) + tail);
  105. });
  106. it('preserves a raw argument while blanking a later C type-keyword argument', () => {
  107. const literal = 'R"TAG(" ), struct Fake, ( ")TAG"';
  108. const source = `take(${literal}, struct RealType);`;
  109. expect(blankCTypeKeywordArgs(source)).toBe(`take(${literal}, RealType);`);
  110. });
  111. it('only counts designators outside raw arguments when blanking a C macro call', () => {
  112. const literal = 'R"TAG(" ) .fake = 1 ( ")TAG"';
  113. const head = 'void reset(void) {\n RESET_THING(';
  114. const tail = ');\n}\n';
  115. expect(blankCDesignatedMacroArgs(head + literal + tail)).toBe(head + literal + tail);
  116. const args = literal + ', .field = 1';
  117. expect(blankCDesignatedMacroArgs(head + args + tail))
  118. .toBe(head + ' '.repeat(args.length) + tail);
  119. });
  120. it.each([
  121. { name: 'C iterator macros', blank: blankCStatementMacroCalls },
  122. { name: 'C type arguments', blank: blankCTypeKeywordArgs },
  123. { name: 'C declaration macros', blank: blankCFileScopePrefixedDeclMacros },
  124. { name: 'C parameterized annotations', blank: blankCParameterizedAnnotationMacros },
  125. { name: 'C designated initializer arguments', blank: blankCDesignatedMacroArgs },
  126. { name: 'C preParse', blank: (source: string) => cExtractor.preParse!(source) },
  127. { name: 'C++ preParse', blank: (source: string) => cppExtractor.preParse!(source, 'template.cpp') },
  128. ])('$name leaves macro-like raw-string contents untouched', ({ blank }) => {
  129. const source = `const auto* text = u8R"TAG(
  130. for_each_item(item, list) {
  131. visit(item);
  132. }
  133. static DECLARE_THING(value);
  134. use(struct Example);
  135. RESET_THING(.field = 1);
  136. class EXAMPLE_API Example {
  137. FORCEINLINE int example() {}
  138. };
  139. FMT_BEGIN_NAMESPACE
  140. int example;
  141. __section(
  142. )TAG";
  143. `;
  144. expect(blank(source)).toBe(source);
  145. });
  146. it('ignores raw-string openers in comments and ordinary literals, then resumes blanking after a real raw string', () => {
  147. const before = [
  148. '// R"COMMENT(',
  149. '/* LR"COMMENT( */',
  150. 'const char* quoted = "escaped R\\"STRING(";',
  151. "const auto digit = 1'000;",
  152. 'const char quote = \'"\';',
  153. scaffoldSource(),
  154. ].join('\n');
  155. const annotation = 'UPROPERTY(EditAnywhere)';
  156. const tail = '\nint actual_field;\n';
  157. expect(blankCppAnnotationMacroCalls(before + annotation + tail))
  158. .toBe(before + ' '.repeat(annotation.length) + tail);
  159. });
  160. });