tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

parseQuery.ts (6520B)


      1 import { assert } from '../../util/util.js';
      2 import {
      3  TestParamsRW,
      4  JSONWithUndefined,
      5  badParamValueChars,
      6  paramKeyIsPublic,
      7 } from '../params_utils.js';
      8 
      9 import { parseParamValue } from './json_param_value.js';
     10 import {
     11  TestQuery,
     12  TestQueryMultiFile,
     13  TestQueryMultiTest,
     14  TestQueryMultiCase,
     15  TestQuerySingleCase,
     16 } from './query.js';
     17 import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
     18 import { validQueryPart } from './validQueryPart.js';
     19 
     20 /**
     21 * converts foo/bar/src/webgpu/this/that/file.spec.ts to webgpu:this,that,file,*
     22 */
     23 function convertPathToQuery(path: string) {
     24  // removes .spec.ts and splits by directory separators.
     25  const parts = path.substring(0, path.length - 8).split(/\/|\\/g);
     26  // Gets parts only after the last `src`. Example: returns ['webgpu', 'foo', 'bar', 'test']
     27  // for ['Users', 'me', 'src', 'cts', 'src', 'webgpu', 'foo', 'bar', 'test']
     28  const partsAfterSrc = parts.slice(parts.lastIndexOf('src') + 1);
     29  const suite = partsAfterSrc.shift();
     30  return `${suite}:${partsAfterSrc.join(',')},*`;
     31 }
     32 
     33 /**
     34 * If a query looks like a path (ends in .spec.ts and has directory separators)
     35 * then convert try to convert it to a query.
     36 */
     37 function convertPathLikeToQuery(queryOrPath: string) {
     38  return queryOrPath.endsWith('.spec.ts') &&
     39    (queryOrPath.includes('/') || queryOrPath.includes('\\'))
     40    ? convertPathToQuery(queryOrPath)
     41    : queryOrPath;
     42 }
     43 
     44 /**
     45 * Convert long suite names (the part before the first colon) to the
     46 * shortest last word
     47 *    foo.bar.moo:test,subtest,foo -> moo:test,subtest,foo
     48 */
     49 function shortenSuiteName(query: string) {
     50  const parts = query.split(':');
     51  // converts foo.bar.moo to moo
     52  const suite = parts.shift()?.replace(/.*\.(\w+)$/, '$1');
     53  return [suite, ...parts].join(':');
     54 }
     55 
     56 export function parseQuery(queryLike: string): TestQuery {
     57  try {
     58    const query = shortenSuiteName(convertPathLikeToQuery(queryLike));
     59    return parseQueryImpl(query);
     60  } catch (ex) {
     61    if (ex instanceof Error) {
     62      ex.message += `\n  on: ${queryLike}`;
     63    }
     64    throw ex;
     65  }
     66 }
     67 
     68 function parseQueryImpl(s: string): TestQuery {
     69  // Undo encodeURIComponentSelectively
     70  s = decodeURIComponent(s);
     71 
     72  // bigParts are: suite, file, test, params (note kBigSeparator could appear in params)
     73  let suite: string;
     74  let fileString: string | undefined;
     75  let testString: string | undefined;
     76  let paramsString: string | undefined;
     77  {
     78    const i1 = s.indexOf(kBigSeparator);
     79    assert(i1 !== -1, `query string must have at least one ${kBigSeparator}`);
     80    suite = s.substring(0, i1);
     81    const i2 = s.indexOf(kBigSeparator, i1 + 1);
     82    if (i2 === -1) {
     83      fileString = s.substring(i1 + 1);
     84    } else {
     85      fileString = s.substring(i1 + 1, i2);
     86      const i3 = s.indexOf(kBigSeparator, i2 + 1);
     87      if (i3 === -1) {
     88        testString = s.substring(i2 + 1);
     89      } else {
     90        testString = s.substring(i2 + 1, i3);
     91        paramsString = s.substring(i3 + 1);
     92      }
     93    }
     94  }
     95 
     96  const { parts: file, wildcard: filePathHasWildcard } = parseBigPart(fileString, kPathSeparator);
     97 
     98  if (testString === undefined) {
     99    // Query is file-level
    100    assert(
    101      filePathHasWildcard,
    102      `File-level query without wildcard ${kWildcard}. Did you want a file-level query \
    103 (append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`
    104    );
    105    return new TestQueryMultiFile(suite, file);
    106  }
    107  assert(!filePathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
    108 
    109  const { parts: test, wildcard: testPathHasWildcard } = parseBigPart(testString, kPathSeparator);
    110 
    111  if (paramsString === undefined) {
    112    // Query is test-level
    113    assert(
    114      testPathHasWildcard,
    115      `Test-level query without wildcard ${kWildcard}; did you want a test-level query \
    116 (append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`
    117    );
    118    assert(file.length > 0, 'File part of test-level query was empty (::)');
    119    return new TestQueryMultiTest(suite, file, test);
    120  }
    121 
    122  // Query is case-level
    123  assert(!testPathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
    124 
    125  const { parts: paramsParts, wildcard: paramsHasWildcard } = parseBigPart(
    126    paramsString,
    127    kParamSeparator
    128  );
    129 
    130  assert(test.length > 0, 'Test part of case-level query was empty (::)');
    131 
    132  const params: TestParamsRW = {};
    133  for (const paramPart of paramsParts) {
    134    const [k, v] = parseSingleParam(paramPart);
    135    assert(validQueryPart.test(k), `param key names must match ${validQueryPart}`);
    136    params[k] = v;
    137  }
    138  if (paramsHasWildcard) {
    139    return new TestQueryMultiCase(suite, file, test, params);
    140  } else {
    141    return new TestQuerySingleCase(suite, file, test, params);
    142  }
    143 }
    144 
    145 // webgpu:a,b,* or webgpu:a,b,c:*
    146 const kExampleQueries = `\
    147 webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}${kWildcard} or \
    148 webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}c${kBigSeparator}${kWildcard}`;
    149 
    150 function parseBigPart(
    151  s: string,
    152  separator: typeof kParamSeparator | typeof kPathSeparator
    153 ): { parts: string[]; wildcard: boolean } {
    154  if (s === '') {
    155    return { parts: [], wildcard: false };
    156  }
    157  const parts = s.split(separator);
    158 
    159  let endsWithWildcard = false;
    160  for (const [i, part] of parts.entries()) {
    161    if (i === parts.length - 1) {
    162      endsWithWildcard = part === kWildcard;
    163    }
    164    assert(
    165      part.indexOf(kWildcard) === -1 || endsWithWildcard,
    166      `Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`
    167    );
    168  }
    169  if (endsWithWildcard) {
    170    // Remove the last element of the array (which is just the wildcard).
    171    parts.length = parts.length - 1;
    172  }
    173  return { parts, wildcard: endsWithWildcard };
    174 }
    175 
    176 function parseSingleParam(paramSubstring: string): [string, JSONWithUndefined] {
    177  assert(paramSubstring !== '', 'Param in a query must not be blank (is there a trailing comma?)');
    178  const i = paramSubstring.indexOf('=');
    179  assert(i !== -1, 'Param in a query must be of form key=value');
    180  const k = paramSubstring.substring(0, i);
    181  assert(paramKeyIsPublic(k), 'Param in a query must not be private (start with _)');
    182  const v = paramSubstring.substring(i + 1);
    183  return [k, parseSingleParamValue(v)];
    184 }
    185 
    186 function parseSingleParamValue(s: string): JSONWithUndefined {
    187  assert(
    188    !badParamValueChars.test(s),
    189    `param value must not match ${badParamValueChars} - was ${s}`
    190  );
    191  return parseParamValue(s);
    192 }