<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayCodegenRunner
 * @flow
 * @format
 */

'use strict';

const RelayCodegenWatcher = require('RelayCodegenWatcher');

const invariant = require('invariant');

const {Map: ImmutableMap} = require('immutable');

import type FileParser from 'FileParser';
import type {CompileResult} from 'RelayCodegenTypes';
import type {File} from 'RelayCodegenTypes';
import type {FileFilter, WatchmanExpression} from 'RelayCodegenWatcher';
import type {RelayReporter} from 'RelayReporter';
import type {GetWriter} from 'getRelayFileWriter';
import type {GraphQLSchema} from 'graphql';

/* eslint-disable no-console-disallow */

export type ParserConfig = {|
  baseDir: string,
  getFileFilter?: (baseDir: string) => FileFilter,
  getParser: (baseDir: string) => FileParser,
  getSchema: () => GraphQLSchema,
  watchmanExpression: WatchmanExpression,
|};

type ParserConfigs = {
  [parser: string]: ParserConfig,
};
type Parsers = {
  [parser: string]: FileParser,
};

export type WriterConfig = {
  parser: string,
  baseParsers?: Array<string>,
  isGeneratedFile: (filePath: string) => boolean,
  getWriter: GetWriter,
};

type WriterConfigs = {
  [writer: string]: WriterConfig,
};

class RelayCodegenRunner {
  parserConfigs: ParserConfigs;
  writerConfigs: WriterConfigs;
  onlyValidate: boolean;
  parsers: Parsers = {};
  // parser => writers that are affected by it
  parserWriters: {[parser: string]: Set<string>};
  _reporter: RelayReporter;

  constructor(options: {
    parserConfigs: ParserConfigs,
    writerConfigs: WriterConfigs,
    onlyValidate: boolean,
    reporter: RelayReporter,
  }) {
    this.parserConfigs = options.parserConfigs;
    this.writerConfigs = options.writerConfigs;
    this.onlyValidate = options.onlyValidate;
    this._reporter = options.reporter;

    this.parserWriters = {};
    for (const parser in options.parserConfigs) {
      this.parserWriters[parser] = new Set();
    }

    for (const writer in options.writerConfigs) {
      const config = options.writerConfigs[writer];
      config.baseParsers &&
        config.baseParsers.forEach(parser =>
          this.parserWriters[parser].add(writer),
        );
      this.parserWriters[config.parser].add(writer);
    }
  }

  async compileAll(): Promise<CompileResult> {
    // reset the parsers
    this.parsers = {};
    for (const parserName in this.parserConfigs) {
      try {
        await this.parseEverything(parserName);
      } catch (e) {
        this._reporter.reportError('RelayCodegenRunner.compileAll', e);
        return 'ERROR';
      }
    }

    let hasChanges = false;
    for (const writerName in this.writerConfigs) {
      const result = await this.write(writerName);
      if (result === 'ERROR') {
        return 'ERROR';
      }
      if (result === 'HAS_CHANGES') {
        hasChanges = true;
      }
    }
    return hasChanges ? 'HAS_CHANGES' : 'NO_CHANGES';
  }

  async compile(writerName: string): Promise<CompileResult> {
    const writerConfig = this.writerConfigs[writerName];

    const parsers = [writerConfig.parser];
    if (writerConfig.baseParsers) {
      writerConfig.baseParsers.forEach(parser => parsers.push(parser));
    }
    // Don't bother resetting the parsers
    await Promise.all(parsers.map(parser => this.parseEverything(parser)));

    return await this.write(writerName);
  }

  async parseEverything(parserName: string): Promise<void> {
    if (this.parsers[parserName]) {
      // no need to parse
      return;
    }<commit_msg>Add option to only build dirty Relay files<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayCodegenRunner
 * @flow
 * @format
 */

'use strict';

const RelayCodegenWatcher = require('RelayCodegenWatcher');
const RelayWatchmanClient = require('RelayWatchmanClient');

const invariant = require('invariant');
const path = require('path');

const {Map: ImmutableMap} = require('immutable');

import type FileParser from 'FileParser';
import type {CompileResult} from 'RelayCodegenTypes';
import type {File} from 'RelayCodegenTypes';
import type {FileFilter, WatchmanExpression} from 'RelayCodegenWatcher';
import type {RelayReporter} from 'RelayReporter';
import type {GetWriter} from 'getRelayFileWriter';
import type {GraphQLSchema} from 'graphql';

/* eslint-disable no-console-disallow */

export type ParserConfig = {|
  baseDir: string,
  getFileFilter?: (baseDir: string) => FileFilter,
  getParser: (baseDir: string) => FileParser,
  getSchema: () => GraphQLSchema,
  watchmanExpression: WatchmanExpression,
|};

type ParserConfigs = {
  [parser: string]: ParserConfig,
};
type Parsers = {
  [parser: string]: FileParser,
};

export type WriterConfig = {
  parser: string,
  baseParsers?: Array<string>,
  isGeneratedFile: (filePath: string) => boolean,
  getWriter: GetWriter,
};

type WriterConfigs = {
  [writer: string]: WriterConfig,
};

class RelayCodegenRunner {
  parserConfigs: ParserConfigs;
  writerConfigs: WriterConfigs;
  onlyValidate: boolean;
  parsers: Parsers = {};
  // parser => writers that are affected by it
  parserWriters: {[parser: string]: Set<string>};
  _reporter: RelayReporter;

  constructor(options: {
    parserConfigs: ParserConfigs,
    writerConfigs: WriterConfigs,
    onlyValidate: boolean,
    reporter: RelayReporter,
  }) {
    this.parserConfigs = options.parserConfigs;
    this.writerConfigs = options.writerConfigs;
    this.onlyValidate = options.onlyValidate;
    this._reporter = options.reporter;

    this.parserWriters = {};
    for (const parser in options.parserConfigs) {
      this.parserWriters[parser] = new Set();
    }

    for (const writer in options.writerConfigs) {
      const config = options.writerConfigs[writer];
      config.baseParsers &&
        config.baseParsers.forEach(parser =>
          this.parserWriters[parser].add(writer),
        );
      this.parserWriters[config.parser].add(writer);
    }
  }

  async compileAll(): Promise<CompileResult> {
    // reset the parsers
    this.parsers = {};
    for (const parserName in this.parserConfigs) {
      try {
        await this.parseEverything(parserName);
      } catch (e) {
        this._reporter.reportError('RelayCodegenRunner.compileAll', e);
        return 'ERROR';
      }
    }

    let hasChanges = false;
    for (const writerName in this.writerConfigs) {
      const result = await this.write(writerName);
      if (result === 'ERROR') {
        return 'ERROR';
      }
      if (result === 'HAS_CHANGES') {
        hasChanges = true;
      }
    }
    return hasChanges ? 'HAS_CHANGES' : 'NO_CHANGES';
  }

  async compile(writerName: string): Promise<CompileResult> {
    const writerConfig = this.writerConfigs[writerName];

    const parsers = [writerConfig.parser];
    if (writerConfig.baseParsers) {
      writerConfig.baseParsers.forEach(parser => parsers.push(parser));
    }
    // Don't bother resetting the parsers
    await Promise.all(parsers.map(parser => this.parseEverything(parser)));

    return await this.write(writerName);
  }

  async getDirtyWriters(filePaths: Array<string>): Promise<Set<string>> {
    const dirtyWriters = new Set();

    // Check if any files are in the output
    for (const configName in this.writerConfigs) {
      const config = this.writerConfigs[configName];
      for (const filePath of filePaths) {
        if (config.isGeneratedFile(filePath)) {
          dirtyWriters.add(configName);
        }
      }
    }

    const client = new RelayWatchmanClient();

    // Check for files in the input
    await Promise.all(
      Object.keys(this.parserConfigs).map(async parserConfigName => {
        const config = this.parserConfigs[parserConfigName];
        const dirs = await client.watchProject(config.baseDir);

        const relativeFilePaths = filePaths.map(filePath =>
          path.relative(config.baseDir, filePath),
        );

        const query = {
          expression: [
            'allof',
            config.watchmanExpression,
            ['name', relativeFilePaths, 'wholename'],
          ],
          fields: ['exists'],
          relative_root: dirs.relativePath,
        };

        const result = await client.command('query', dirs.root, query);
        if (result.files.length > 0) {
          this.parserWriters[parserConfigName].forEach(writerName =>
            dirtyWriters.add(writerName),
          );
        }
      }),
    );

    client.end();
    return dirtyWriters;
  }

  async parseEverything(parserName: string): Promise<void> {
    if (this.parsers[parserName]) {
      // no need to parse
      return;
    }
{'fraction_non_alphanumeric': 0.11364628820960698, 'fraction_numerical': 0.000982532751091703, 'mean_word_length': 3.4106884930187773, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7486115', 'n_tokens_mistral': 2752, 'n_tokens_neox': 2702, 'n_words': 679}