Stryker

gather/gather-runner.js - Stryker report

File / Directory
Mutation score
# Killed
# Survived
# Timeout
# No coverage
# Runtime errors
# Transpile errors
Total detected
Total undetected
Total mutants
gather/gather-runner.js
78.72 %
78.72 185 50 0 0 0 0 185 50 235
Expand all
/**
 * @license Copyright 2016 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

const log = require(0'lighthouse-logger');
const manifestParser = require(1'../lib/manifest-parser.js');
const LHError = require(2'../lib/lh-error.js');
const URL = require(3'../lib/url-shim.js');
const NetworkRecorder = require(4'../lib/network-recorder.js');
const constants = require(5'../config/constants.js');

const Driver = require(6'../gather/driver.js'); // eslint-disable-line no-unused-vars

/** @typedef {import('./gatherers/gatherer.js').PhaseResult} PhaseResult */
/**
 * Each entry in each gatherer result array is the output of a gatherer phase:
 * `beforePass`, `pass`, and `afterPass`. Flattened into an `LH.Artifacts` in
 * `collectArtifacts`.
 * @typedef {Record<keyof LH.GathererArtifacts, Array<PhaseResult|Promise<PhaseResult>>>} GathererResults
 */
/** @typedef {Array<[keyof GathererResults, GathererResults[keyof GathererResults]]>} GathererResultsEntries */
/**
 * Class that drives browser to load the page and runs gatherer lifecycle hooks.
 * Execution sequence when GatherRunner.run() is called:
 *
 * 1. Setup
 *   A. driver.connect()
 *   B. GatherRunner.setupDriver()
 *     i. assertNoSameOriginServiceWorkerClients
 *     ii. retrieve and save userAgent
 *     iii. beginEmulation
 *     iv. enableRuntimeEvents
 *     v. evaluateScriptOnLoad rescue native Promise from potential polyfill
 *     vi. register a performance observer
 *     vii. register dialog dismisser
 *     viii. clearDataForOrigin
 *
 * 2. For each pass in the config:
 *   A. GatherRunner.beforePass()
 *     i. navigate to about:blank
 *     ii. Enable network request blocking for specified patterns
 *     iii. all gatherers' beforePass()
 *   B. GatherRunner.pass()
 *     i. cleanBrowserCaches() (if it's a perf run)
 *     ii. beginDevtoolsLog()
 *     iii. beginTrace (if requested)
 *     iv. GatherRunner.loadPage()
 *       a. navigate to options.url (and wait for onload)
 *     v. all gatherers' pass()
 *   C. GatherRunner.afterPass()
 *     i. endTrace (if requested) & endDevtoolsLog & endThrottling
 *     ii. all gatherers' afterPass()
 *
 * 3. Teardown
 *   A. clearDataForOrigin
 *   B. GatherRunner.disposeDriver()
 *   C. collect all artifacts and return them
 *     i. collectArtifacts() from completed passes on each gatherer
 *     ii. add trace and devtoolsLog data
 */
class GatherRunner {
  /**
   * Loads about:blank and waits there briefly. Since a Page.reload command does
   * not let a service worker take over, we navigate away and then come back to
   * reload. We do not `waitForLoad` on about:blank since a page load event is
   * never fired on it.
   * @param {Driver} driver
   * @param {string=} url
   * @return {Promise<void>}
   */
  static async loadBlank(driver, url = constants.defaultPassConfig.blankPage) 7{
    const status = 8{msg: 9'Resetting state with about:blank', id: 10'lh:gather:loadBlank'};
    log.time(status);
    await driver.gotoURL(url, 11{waitForNavigated: 12true});
    log.timeEnd(status);
  }

  /**
   * Loads options.url with specified options. If the main document URL
   * redirects, options.url will be updated accordingly. As such, options.url
   * will always represent the post-redirected URL. options.requestedUrl is the
   * pre-redirect starting URL.
   * @param {Driver} driver
   * @param {LH.Gatherer.PassContext} passContext
   * @return {Promise<void>}
   */
  static async loadPage(driver, passContext) 13{
    const finalUrl = await driver.gotoURL(passContext.url, 14{
      waitForFCP: passContext.passConfig.recordTrace,
      waitForLoad: 15true,
      passContext,
    });
    passContext.url = finalUrl;
  }

  /**
   * @param {Driver} driver
   * @param {{requestedUrl: string, settings: LH.Config.Settings}} options
   * @return {Promise<void>}
   */
  static async setupDriver(driver, options) 16{
    const status = 17{msg: 18'Initializing…', id: 19'lh:gather:setupDriver'};
    log.time(status);
    const resetStorage = 20!options.settings.disableStorageReset;
    await driver.assertNoSameOriginServiceWorkerClients(options.requestedUrl);
    await driver.beginEmulation(options.settings);
    await driver.enableRuntimeEvents();
    await driver.cacheNatives();
    await driver.registerPerformanceObserver();
    await driver.dismissJavaScriptDialogs();
    if (2122resetStorage) await driver.clearDataForOrigin(options.requestedUrl);
    log.timeEnd(status);
  }

  /**
   * @param {Driver} driver
   * @return {Promise<void>}
   */
  static async disposeDriver(driver) 23{
    const status = 24{msg: 25'Disconnecting from browser...', id: 26'lh:gather:disconnect'};

    log.time(status);
    try 27{
      await driver.disconnect();
    } catch (err) 28{
      // Ignore disconnecting error if browser was already closed.
      // See https://github.com/GoogleChrome/lighthouse/issues/1583
      if (293031!(/close\/.*status: (500|404)$/.test(err.message))) 32{
        log.error(33'GatherRunner disconnect', err.message);
      }
    }
    log.timeEnd(status);
  }

  /**
   * Returns an error if the original network request failed or wasn't found.
   * @param {string} url The URL of the original requested page.
   * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
   * @return {LHError|undefined}
   */
  static getPageLoadError(url, networkRecords) 34{
    const mainRecord = networkRecords.find(record => 35{
      // record.url is actual request url, so needs to be compared without any URL fragment.
      return URL.equalWithExcludedFragments(record.url, url);
    });

    if (363738!mainRecord) 39{
      return new LHError(LHError.errors.NO_DOCUMENT_REQUEST);
    } else if (4041mainRecord.failed) 42{
      const netErr = mainRecord.localizedFailDescription;
      // Match all resolution and DNS failures
      // https://cs.chromium.org/chromium/src/net/base/net_error_list.h?rcl=cd62979b
      if (
        434445464748495051netErr === 52'net::ERR_NAME_NOT_RESOLVED' ||
        535455netErr === 56'net::ERR_NAME_RESOLUTION_FAILED' ||
        netErr.startsWith(57'net::ERR_DNS_')
      ) 58{
        return new LHError(LHError.errors.DNS_FAILURE);
      } else 59{
        return new LHError(
          LHError.errors.FAILED_DOCUMENT_REQUEST,
          60{errorDetails: netErr}
        );
      }
    } else if (6162mainRecord.hasErrorStatusCode()) 63{
      return new LHError(
        LHError.errors.ERRORED_DOCUMENT_REQUEST,
        64{statusCode: 65`${mainRecord.statusCode}`}
      );
    }
  }

  /**
   * Calls beforePass() on gatherers before tracing
   * has started and before navigation to the target page.
   * @param {LH.Gatherer.PassContext} passContext
   * @param {Partial<GathererResults>} gathererResults
   * @return {Promise<void>}
   */
  static async beforePass(passContext, gathererResults) 66{
    const bpStatus = 67{msg: 68`Running beforePass methods`, id: 69`lh:gather:beforePass`};
    log.time(bpStatus, 70'verbose');
    const blockedUrls = (717273passContext.passConfig.blockedUrlPatterns || [])
      .concat(747576passContext.settings.blockedUrlPatterns || []);

    // Set request blocking before any network activity
    // No "clearing" is done at the end of the pass since blockUrlPatterns([]) will unset all if
    // neccessary at the beginning of the next pass.
    await passContext.driver.blockUrlPatterns(blockedUrls);
    await passContext.driver.setExtraHTTPHeaders(passContext.settings.extraHeaders);

    for (const gathererDefn of passContext.passConfig.gatherers) 77{
      const gatherer = gathererDefn.instance;
      // Abuse the passContext to pass through gatherer options
      passContext.options = 787980gathererDefn.options || {};
      const status = 81{
        msg: 82`Retrieving setup: ${gatherer.name}`,
        id: 83`lh:gather:beforePass:${gatherer.name}`,
      };
      log.time(status, 84'verbose');
      const artifactPromise = Promise.resolve().then(_ => gatherer.beforePass(passContext));
      gathererResults[gatherer.name] = 85[artifactPromise];
      await artifactPromise.catch(() => {});
      log.timeEnd(status);
    }
    log.timeEnd(bpStatus);
  }

  /**
   * Navigates to requested URL and then runs pass() on gatherers while trace
   * (if requested) is still being recorded.
   * @param {LH.Gatherer.PassContext} passContext
   * @param {Partial<GathererResults>} gathererResults
   * @return {Promise<void>}
   */
  static async pass(passContext, gathererResults) 86{
    const driver = passContext.driver;
    const config = passContext.passConfig;
    const settings = passContext.settings;
    const gatherers = config.gatherers;

    const recordTrace = config.recordTrace;
    const isPerfRun = 87888990919293!settings.disableStorageReset && recordTrace && config.useThrottling;

    const status = 94{
      msg: 95'Loading page & waiting for onload',
      id: 96`lh:gather:loadPage-${passContext.passConfig.passName}`,
      args: 97[gatherers.map(g => g.instance.name).join(98', ')],
    };
    log.time(status);

    // Clear disk & memory cache if it's a perf run
    if (99100isPerfRun) await driver.cleanBrowserCaches();
    // Always record devtoolsLog
    await driver.beginDevtoolsLog();
    // Begin tracing if requested by config.
    if (101102recordTrace) await driver.beginTrace(settings);

    // Navigate.
    await GatherRunner.loadPage(driver, passContext);
    log.timeEnd(status);

    const pStatus = 103{msg: 104`Running pass methods`, id: 105`lh:gather:pass`};
    log.time(pStatus, 106'verbose');
    for (const gathererDefn of gatherers) 107{
      const gatherer = gathererDefn.instance;
      // Abuse the passContext to pass through gatherer options
      passContext.options = 108109110gathererDefn.options || {};
      const status = 111{
        msg: 112`Retrieving in-page: ${gatherer.name}`,
        id: 113`lh:gather:pass:${gatherer.name}`,
      };
      log.time(status);
      const artifactPromise = Promise.resolve().then(_ => gatherer.pass(passContext));

      const gathererResult = 114115116gathererResults[gatherer.name] || [];
      gathererResult.push(artifactPromise);
      gathererResults[gatherer.name] = gathererResult;
      await artifactPromise.catch(() => {});
    }
    log.timeEnd(status);
    log.timeEnd(pStatus);
  }

  /**
   * Ends tracing and collects trace data (if requested for this pass), and runs
   * afterPass() on gatherers with trace data passed in. Promise resolves with
   * object containing trace and network data.
   * @param {LH.Gatherer.PassContext} passContext
   * @param {Partial<GathererResults>} gathererResults
   * @return {Promise<LH.Gatherer.LoadData>}
   */
  static async afterPass(passContext, gathererResults) 117{
    const driver = passContext.driver;
    const config = passContext.passConfig;
    const gatherers = config.gatherers;

    let trace;
    if (118119config.recordTrace) 120{
      const status = 121{msg: 122'Retrieving trace', id: 123`lh:gather:getTrace`};
      log.time(status);
      trace = await driver.endTrace();
      log.timeEnd(status);
    }

    const status = 124{
      msg: 125'Retrieving devtoolsLog & network records',
      id: 126`lh:gather:getDevtoolsLog`,
    };
    log.time(status);
    const devtoolsLog = driver.endDevtoolsLog();
    const networkRecords = NetworkRecorder.recordsFromLogs(devtoolsLog);
    log.timeEnd(status);

    let pageLoadError = GatherRunner.getPageLoadError(passContext.url, networkRecords);
    // If the driver was offline, a page load error is expected, so do not save it.
    if (127128129!driver.online) pageLoadError = undefined;

    if (130131pageLoadError) 132{
      log.error(133'GatherRunner', pageLoadError.message, passContext.url);
      passContext.LighthouseRunWarnings.push(pageLoadError.friendlyMessage);
    }

    // Expose devtoolsLog, networkRecords, and trace (if present) to gatherers
    /** @type {LH.Gatherer.LoadData} */
    const passData = 134{
      networkRecords,
      devtoolsLog,
      trace,
    };

    const apStatus = 135{msg: 136`Running afterPass methods`, id: 137`lh:gather:afterPass`};
    // Disable throttling so the afterPass analysis isn't throttled
    await driver.setThrottling(passContext.settings, 138{useThrottling: 139false});
    log.time(apStatus, 140'verbose');

    for (const gathererDefn of gatherers) 141{
      const gatherer = gathererDefn.instance;
      const status = 142{
        msg: 143`Retrieving: ${gatherer.name}`,
        id: 144`lh:gather:afterPass:${gatherer.name}`,
      };
      log.time(status);

      // Add gatherer options to the passContext.
      passContext.options = 145146147gathererDefn.options || {};

      // If there was a pageLoadError, fail every afterPass with it rather than bail completely.
      const artifactPromise = pageLoadError ?
        Promise.reject(pageLoadError) :
        // Wrap gatherer response in promise, whether rejected or not.
        Promise.resolve().then(_ => gatherer.afterPass(passContext, passData));

      const gathererResult = 148149150gathererResults[gatherer.name] || [];
      gathererResult.push(artifactPromise);
      gathererResults[gatherer.name] = gathererResult;
      await artifactPromise.catch(() => {});
      log.timeEnd(status);
    }
    log.timeEnd(apStatus);

    // Resolve on tracing data using passName from config.
    return passData;
  }

  /**
   * Takes the results of each gatherer phase for each gatherer and uses the
   * last produced value (that's not undefined) as the artifact for that
   * gatherer. If an error was rejected from a gatherer phase,
   * uses that error object as the artifact instead.
   * @param {Partial<GathererResults>} gathererResults
   * @param {LH.BaseArtifacts} baseArtifacts
   * @return {Promise<LH.Artifacts>}
   */
  static async collectArtifacts(gathererResults, baseArtifacts) 151{
    /** @type {Partial<LH.GathererArtifacts>} */
    const gathererArtifacts = {};

    const resultsEntries = /** @type {GathererResultsEntries} */ (Object.entries(gathererResults));
    for (const [gathererName, phaseResultsPromises] of resultsEntries) 152{
      if (153154155gathererArtifacts[gathererName] !== undefined) continue;

      try 156{
        const phaseResults = await Promise.all(phaseResultsPromises);
        // Take last defined pass result as artifact.
        const definedResults = phaseResults.filter(element => 157158159element !== undefined);
        const artifact = definedResults[160definedResults.length - 1];
        // Typecast pretends artifact always provided here, but checked below for top-level `throw`.
        gathererArtifacts[gathererName] = /** @type {NonVoid<PhaseResult>} */ (artifact);
      } catch (err) 161{
        // Return error to runner to handle turning it into an error audit.
        gathererArtifacts[gathererName] = err;
      }

      if (162163164gathererArtifacts[gathererName] === undefined) 165{
        throw new Error(166`${gathererName} failed to provide an artifact.`);
      }
    }

    // Take only unique LighthouseRunWarnings.
    baseArtifacts.LighthouseRunWarnings = Array.from(new Set(baseArtifacts.LighthouseRunWarnings));

    // Take the timing entries we've gathered so far.
    baseArtifacts.Timing = log.getTimeEntries();

    // TODO(bckenny): correct Partial<LH.GathererArtifacts> at this point to drop cast.
    return /** @type {LH.Artifacts} */ (167{...baseArtifacts, ...gathererArtifacts});
  }

  /**
   * @param {{driver: Driver, requestedUrl: string, settings: LH.Config.Settings}} options
   * @return {Promise<LH.BaseArtifacts>}
   */
  static async getBaseArtifacts(options) 168{
    const hostUserAgent = (await options.driver.getBrowserVersion()).userAgent;

    const {emulatedFormFactor} = options.settings;
    // Whether Lighthouse was run on a mobile device (i.e. not on a desktop machine).
    const IsMobileHost = 169170171hostUserAgent.includes(172'Android') || hostUserAgent.includes(173'Mobile');
    const TestedAsMobileDevice = 174175176177178179emulatedFormFactor === 180'mobile' ||
      (181182183184185186emulatedFormFactor !== 187'desktop' && IsMobileHost);

    return 188{
      fetchTime: (new Date()).toJSON(),
      LighthouseRunWarnings: [],
      TestedAsMobileDevice,
      HostUserAgent: hostUserAgent,
      NetworkUserAgent: 189'', // updated later
      BenchmarkIndex: 0, // updated later
      WebAppManifest: null, // updated later
      traces: {},
      devtoolsLogs: {},
      settings: options.settings,
      URL: 190{requestedUrl: options.requestedUrl, finalUrl: 191''},
      Timing: [],
    };
  }

  /**
   * Uses the debugger protocol to fetch the manifest from within the context of
   * the target page, reusing any credentials, emulation, etc, already established
   * there.
   *
   * Returns the parsed manifest or null if the page had no manifest. If the manifest
   * was unparseable as JSON, manifest.value will be undefined and manifest.warning
   * will have the reason. See manifest-parser.js for more information.
   *
   * @param {LH.Gatherer.PassContext} passContext
   * @return {Promise<LH.Artifacts.Manifest|null>}
   */
  static async getWebAppManifest(passContext) 192{
    const response = await passContext.driver.getAppManifest();
    if (193194195!response) return null;
    return manifestParser(response.data, response.url, passContext.url);
  }

  /**
   * @param {Array<LH.Config.Pass>} passes
   * @param {{driver: Driver, requestedUrl: string, settings: LH.Config.Settings}} options
   * @return {Promise<LH.Artifacts>}
   */
  static async run(passes, options) 196{
    const driver = options.driver;

    /** @type {Partial<GathererResults>} */
    const gathererResults = {};

    try 197{
      await driver.connect();
      const baseArtifacts = await GatherRunner.getBaseArtifacts(options);
      // In the devtools/extension case, we can't still be on the site while trying to clear state
      // So we first navigate to about:blank, then apply our emulation & setup
      await GatherRunner.loadBlank(driver);
      baseArtifacts.BenchmarkIndex = await options.driver.getBenchmarkIndex();
      await GatherRunner.setupDriver(driver, options);

      // Run each pass
      let isFirstPass = 198true;
      for (const passConfig of passes) 199{
        const passContext = 200{
          driver: options.driver,
          // If the main document redirects, we'll update this to keep track
          url: options.requestedUrl,
          settings: options.settings,
          passConfig,
          baseArtifacts,
          // *pass() functions and gatherers can push to this warnings array.
          LighthouseRunWarnings: baseArtifacts.LighthouseRunWarnings,
        };

        await driver.setThrottling(options.settings, passConfig);
        if (201202203!isFirstPass) 204{
          // Already on blank page if driver was just set up.
          await GatherRunner.loadBlank(driver, passConfig.blankPage);
        }
        await GatherRunner.beforePass(passContext, gathererResults);
        await GatherRunner.pass(passContext, gathererResults);
        if (205206isFirstPass) 207{
          baseArtifacts.WebAppManifest = await GatherRunner.getWebAppManifest(passContext);
        }
        const passData = await GatherRunner.afterPass(passContext, gathererResults);

        // Save devtoolsLog, but networkRecords are discarded and not added onto artifacts.
        baseArtifacts.devtoolsLogs[passConfig.passName] = passData.devtoolsLog;

        const userAgentEntry = passData.devtoolsLog.find(entry =>
          208209210211212213entry.method === 214'Network.requestWillBeSent' &&
          215!216!entry.params.request.headers[217'User-Agent']
        );

        if (218219220userAgentEntry && 221!baseArtifacts.NetworkUserAgent) 222{
          // @ts-ignore - guaranteed to exist by the find above
          baseArtifacts.NetworkUserAgent = userAgentEntry.params.request.headers[223'User-Agent'];
        }

        // If requested by config, save pass's trace.
        if (224225passData.trace) 226{
          baseArtifacts.traces[passConfig.passName] = passData.trace;
        }

        if (227228isFirstPass) 229{
          // Copy redirected URL to artifact in the first pass only.
          baseArtifacts.URL.finalUrl = passContext.url;
          isFirstPass = 230false;
        }
      }
      const resetStorage = 231!options.settings.disableStorageReset;
      if (232233resetStorage) await driver.clearDataForOrigin(options.requestedUrl);
      await GatherRunner.disposeDriver(driver);
      return GatherRunner.collectArtifacts(gathererResults, baseArtifacts);
    } catch (err) 234{
      // cleanup on error
      GatherRunner.disposeDriver(driver);
      throw err;
    }
  }
}

module.exports = GatherRunner;
# Mutator State Location Original Replacement
0 StringLiteral Killed 7 : 20 ' ... ' ""
1 StringLiteral Killed 8 : 31 '../ ... . ' ""
2 StringLiteral Killed 9 : 24 '../ ... . ' ""
3 StringLiteral Killed 10 : 20 '../ ... . ' ""
4 StringLiteral Killed 11 : 32 '../ ... . ' ""
5 StringLiteral Killed 12 : 26 '../ ... . ' ""
6 StringLiteral Killed 14 : 23 '../ ... . ' ""
7 Block Survived 73 : 78 { ...; } {}
8 ObjectLiteral Killed 74 : 19 { :... '} {}
9 StringLiteral Survived 74 : 25 ' ... ' ""
10 StringLiteral Killed 74 : 65 ' : ... ' ""
11 ObjectLiteral Survived 76 : 30 { ... } {}
12 BooleanSubstitution Survived 76 : 49
13 Block Killed 89 : 45 { ...; } {}
14 ObjectLiteral Survived 90 : 59 { ... } {}
15 BooleanSubstitution Survived 92 : 19
16 Block Killed 103 : 44 { ...; } {}
17 ObjectLiteral Killed 104 : 19 { :... '} {}
18 StringLiteral Survived 104 : 25 ' …' ""
19 StringLiteral Killed 104 : 46 ' : ... ' ""
20 PrefixUnaryExpression Killed 106 : 25 ! ... . . .
21 IfStatement Killed 113 : 8
22 IfStatement Killed 113 : 8
23 Block Survived 121 : 37 { ...; } {}
24 ObjectLiteral Killed 122 : 19 { :... '} {}
25 StringLiteral Survived 122 : 25 ' ... ...' ""
26 StringLiteral Killed 122 : 62 ' : ... ' ""
27 Block Survived 125 : 8 { ... } {}
28 Block Survived 127 : 18 { ... } {}
29 IfStatement Survived 130 : 10 !(/ ... ))
30 IfStatement Survived 130 : 10 !(/ ... ))
31 PrefixUnaryExpression Killed 130 : 10 !(/ ... ) / ... )
32 Block Survived 130 : 62 { ... } {}
33 StringLiteral Survived 131 : 18 ' ... ' ""
34 Block Killed 143 : 47 { ...} } {}
35 Block Killed 144 : 53 { ... } {}
36 IfStatement Killed 149 : 8 !
37 IfStatement Killed 149 : 8 !
38 PrefixUnaryExpression Killed 149 : 8 !
39 Block Killed 149 : 21 { ... } {}
40 IfStatement Killed 151 : 15 .
41 IfStatement Killed 151 : 15 .
42 Block Killed 151 : 34 { ... } {}
43 IfStatement Killed 156 : 8 === '... ')
44 IfStatement Killed 156 : 8 === '... ')
45 BinaryExpression Killed 156 : 8 === '... ') ( ... ')
46 BinaryExpression Killed 156 : 8 === '... ' === '... '
47 ConditionalExpression Killed 156 : 8 === '... '
48 ConditionalExpression Killed 156 : 8 === '... '
49 BinaryExpression Killed 156 : 8 === '... ' !== '... '
50 ConditionalExpression Killed 156 : 8 === '... '
51 ConditionalExpression Killed 156 : 8 === '... '
52 StringLiteral Killed 156 : 19 ' :... ' ""
53 BinaryExpression Killed 157 : 8 === '... ' !== '... '
54 ConditionalExpression Killed 157 : 8 === '... '
55 ConditionalExpression Survived 157 : 8 === '... '
56 StringLiteral Survived 157 : 19 ' :... ' ""
57 StringLiteral Killed 158 : 26 ' :: ' ""
58 Block Killed 159 : 8 { ... } {}
59 Block Killed 161 : 13 { ... } {}
60 ObjectLiteral Killed 164 : 10 { ... } {}
61 IfStatement Killed 167 : 15 . ... ()
62 IfStatement Killed 167 : 15 . ... ()
63 Block Killed 167 : 48 { ... } {}
64 ObjectLiteral Killed 170 : 8 { ... }`} {}
65 StringLiteral Killed 170 : 21 `${ ... }` ""
66 Block Killed 182 : 56 { ...; } {}
67 ObjectLiteral Killed 183 : 21 { :... `} {}
68 StringLiteral Survived 183 : 27 ` ... ` ""
69 StringLiteral Killed 183 : 61 ` : ... ` ""
70 StringLiteral Killed 184 : 23 ' ' ""
71 BinaryExpression Killed 185 : 25 . ...|| [] . ...&& []
72 ConditionalExpression Killed 185 : 25 . ...|| []
73 ConditionalExpression Killed 185 : 25 . ...|| []
74 BinaryExpression Killed 186 : 14 . ...|| [] . ...&& []
75 ConditionalExpression Killed 186 : 14 . ...|| []
76 ConditionalExpression Killed 186 : 14 . ...|| []
77 Block Killed 194 : 65 { ... } {}
78 BinaryExpression Killed 197 : 28 . || {} . && {}
79 ConditionalExpression Killed 197 : 28 . || {}
80 ConditionalExpression Killed 197 : 28 . || {}
81 ObjectLiteral Killed 198 : 21 { ... } {}
82 StringLiteral Survived 199 : 13 ` ... }` ""
83 StringLiteral Killed 200 : 12 ` : ... }` ""
84 StringLiteral Killed 202 : 23 ' ' ""
85 ArrayLiteral Killed 204 : 39 [ ... ] []
86 Block Killed 218 : 50 { ...; } {}
87 BinaryExpression Killed 225 : 22 ! ... . ! ... .
88 ConditionalExpression Killed 225 : 22 ! ... .
89 ConditionalExpression Killed 225 : 22 ! ... .
90 BinaryExpression Killed 225 : 22 ! ... && ! ... ||
91 ConditionalExpression Killed 225 : 22 ! ... &&
92 PrefixUnaryExpression Killed 225 : 22 ! . .
93 ConditionalExpression Survived 225 : 22 ! ... &&
94 ObjectLiteral Killed 227 : 19 { ... } {}
95 StringLiteral Survived 228 : 11 ' ... ' ""
96 StringLiteral Killed 229 : 10 ` : ... }` ""
97 ArrayLiteral Survived 230 : 12 [ ..., ')] []
98 StringLiteral Survived 230 : 54 ', ' ""
99 IfStatement Killed 235 : 8
100 IfStatement Killed 235 : 8
101 IfStatement Killed 239 : 8
102 IfStatement Killed 239 : 8
103 ObjectLiteral Killed 245 : 20 { :... `} {}
104 StringLiteral Survived 245 : 26 ` ... ` ""
105 StringLiteral Killed 245 : 54 ` : ... ` ""
106 StringLiteral Killed 246 : 22 ' ' ""
107 Block Killed 247 : 42 { ... } {}
108 ConditionalExpression Killed 250 : 28 . || {}
109 BinaryExpression Killed 250 : 28 . || {} . && {}
110 ConditionalExpression Killed 250 : 28 . || {}
111 ObjectLiteral Killed 251 : 21 { ... } {}
112 StringLiteral Survived 252 : 13 ` ... }` ""
113 StringLiteral Killed 253 : 12 ` : ... }` ""
114 BinaryExpression Killed 258 : 29 [ ...|| [] [ ...&& []
115 ConditionalExpression Killed 258 : 29 [ ...|| []
116 ConditionalExpression Killed 258 : 29 [ ...|| []
117 Block Killed 275 : 55 { ...; } {}
118 IfStatement Killed 281 : 8 .
119 IfStatement Killed 281 : 8 .
120 Block Killed 281 : 28 { ... } {}
121 ObjectLiteral Killed 282 : 21 { :... `} {}
122 StringLiteral Survived 282 : 27 ' ... ' ""
123 StringLiteral Killed 282 : 51 ` : ... ` ""
124 ObjectLiteral Killed 288 : 19 { ... } {}
125 StringLiteral Survived 289 : 11 ' ... ' ""
126 StringLiteral Killed 290 : 10 ` : ... ` ""
127 IfStatement Killed 299 : 8 ! .
128 IfStatement Killed 299 : 8 ! .
129 PrefixUnaryExpression Killed 299 : 8 ! . .
130 IfStatement Killed 301 : 8
131 IfStatement Killed 301 : 8
132 Block Killed 301 : 23 { ... } {}
133 StringLiteral Survived 302 : 16 ' ' ""
134 ObjectLiteral Killed 308 : 21 { ... } {}
135 ObjectLiteral Killed 314 : 21 { :... `} {}
136 StringLiteral Survived 314 : 27 ` ... ` ""
137 StringLiteral Killed 314 : 60 ` : ... ` ""
138 ObjectLiteral Survived 316 : 53 { ... } {}
139 BooleanSubstitution Survived 316 : 69
140 StringLiteral Killed 317 : 23 ' ' ""
141 Block Killed 319 : 42 { ... } {}
142 ObjectLiteral Killed 321 : 21 { ... } {}
143 StringLiteral Survived 322 : 13 ` ... }` ""
144 StringLiteral Killed 323 : 12 ` : ... }` ""
145 ConditionalExpression Killed 328 : 28 . || {}
146 BinaryExpression Killed 328 : 28 . || {} . && {}
147 ConditionalExpression Killed 328 : 28 . || {}
148 BinaryExpression Killed 336 : 29 [ ...|| [] [ ...&& []
149 ConditionalExpression Killed 336 : 29 [ ...|| []
150 ConditionalExpression Killed 336 : 29 [ ...|| []
151 Block Killed 357 : 64 { ...; } {}
152 Block Killed 362 : 71 { ... } {}
153 IfStatement Killed 363 : 10 [ ...] !==
154 IfStatement Killed 363 : 10 [ ...] !==
155 BinaryExpression Killed 363 : 10 [ ...] !== [ ...] ===
156 Block Killed 365 : 10 { ... } {}
157 BinaryExpression Killed 368 : 62 !== ===
158 ConditionalExpression Killed 368 : 62 !==
159 ConditionalExpression Killed 368 : 62 !==
160 BinaryExpression Killed 369 : 40 . - . +
161 Block Killed 372 : 20 { ... } {}
162 IfStatement Killed 377 : 10 [ ...] ===
163 IfStatement Killed 377 : 10 [ ...] ===
164 BinaryExpression Killed 377 : 10 [ ...] === [ ...] !==
165 Block Killed 377 : 57 { ... } {}
166 StringLiteral Survived 378 : 24 `${ ... .` ""
167 ObjectLiteral Killed 389 : 40 {... ... } /** @...*/ {}
168 Block Killed 396 : 41 { ...; } {}
169 ConditionalExpression Killed 401 : 25 . ... ')
170 BinaryExpression Survived 401 : 25 . ... ') . ... ')
171 ConditionalExpression Killed 401 : 25 . ... ')
172 StringLiteral Killed 401 : 48 ' ' ""
173 StringLiteral Killed 401 : 85 ' ' ""
174 BinaryExpression Killed 402 : 33 === '... ) === '... ' &&
175 ConditionalExpression Killed 402 : 33 === '... )
176 ConditionalExpression Killed 402 : 33 === '... )
177 BinaryExpression Killed 402 : 33 === ' ' !== ' '
178 ConditionalExpression Killed 402 : 33 === ' '
179 ConditionalExpression Killed 402 : 33 === ' '
180 StringLiteral Killed 402 : 56 ' ' ""
181 BinaryExpression Killed 403 : 7 !== '... ' && !== '... ' ||
182 ConditionalExpression Killed 403 : 7 !== '... ' &&
183 ConditionalExpression Killed 403 : 7 !== '... ' &&
184 BinaryExpression Killed 403 : 7 !== ' ' === ' '
185 ConditionalExpression Killed 403 : 7 !== ' '
186 ConditionalExpression Killed 403 : 7 !== ' '
187 StringLiteral Killed 403 : 30 ' ' ""
188 ObjectLiteral Killed 405 : 11 { ... } {}
189 StringLiteral Killed 410 : 24 '' " ... !"
190 ObjectLiteral Killed 416 : 11 { ...: ''} {}
191 StringLiteral Survived 416 : 58 '' " ... !"
192 Block Killed 433 : 46 { ...; } {}
193 IfStatement Killed 435 : 8 !
194 IfStatement Killed 435 : 8 !
195 PrefixUnaryExpression Killed 435 : 8 !
196 Block Killed 444 : 36 { ...} } {}
197 Block Killed 450 : 8 { ... } {}
198 BooleanSubstitution Killed 460 : 24
199 Block Killed 461 : 39 { ... } {}
200 ObjectLiteral Killed 462 : 28 { ... } {}
201 IfStatement Survived 474 : 12 !
202 IfStatement Survived 474 : 12 !
203 PrefixUnaryExpression Survived 474 : 12 !
204 Block Survived 474 : 26 { ... } {}
205 IfStatement Survived 480 : 12
206 IfStatement Survived 480 : 12
207 Block Survived 480 : 25 { ... } {}
208 BinaryExpression Killed 489 : 10 . ... '] . ... ']
209 ConditionalExpression Killed 489 : 10 . ... ']
210 BinaryExpression Killed 489 : 10 . ... ' . ... '
211 ConditionalExpression Killed 489 : 10 . ... '
212 ConditionalExpression Survived 489 : 10 . ... ']
213 ConditionalExpression Killed 489 : 10 . ... '
214 StringLiteral Killed 489 : 27 ' ... ' ""
215 PrefixUnaryExpression Killed 490 : 10 !! ... '] ! ... ']
216 PrefixUnaryExpression Killed 490 : 11 ! ... '] . ... ']
217 StringLiteral Killed 490 : 41 ' - ' ""
218 IfStatement Killed 493 : 12 && ! ... .
219 IfStatement Survived 493 : 12 && ! ... .
220 BinaryExpression Survived 493 : 12 && ! ... . || ! ... .
221 PrefixUnaryExpression Killed 493 : 30 ! . .
222 Block Killed 493 : 63 { ... } {}
223 StringLiteral Killed 495 : 81 ' - ' ""
224 IfStatement Killed 499 : 12 .
225 IfStatement Killed 499 : 12 .
226 Block Killed 499 : 28 { ... } {}
227 IfStatement Killed 503 : 12
228 IfStatement Survived 503 : 12
229 Block Killed 503 : 25 { ... } {}
230 BooleanSubstitution Survived 506 : 24
231 PrefixUnaryExpression Survived 509 : 27 ! ... . . .
232 IfStatement Survived 510 : 10
233 IfStatement Survived 510 : 10
234 Block Killed 513 : 18 { ... } {}