<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule RelayFileWriter
 * @flow
 * @format
 */

'use strict';

const RelayCompiler = require('../RelayCompiler');
const RelayFlowGenerator = require('../core/RelayFlowGenerator');
const RelayParser = require('../core/RelayParser');
const RelayValidator = require('../core/RelayValidator');

const crypto = require('crypto');
const graphql = require('graphql');
const invariant = require('invariant');
const path = require('path');
const writeRelayGeneratedFile = require('./writeRelayGeneratedFile');

const {generate} = require('../core/RelayCodeGenerator');
const {
  ASTConvert,
  CodegenDirectory,
  CompilerContext,
  Profiler,
  SchemaUtils,
} = require('graphql-compiler');
const {Map: ImmutableMap} = require('immutable');

import type {ScalarTypeMapping} from '../core/RelayFlowTypeTransformers';
import type {FormatModule} from './writeRelayGeneratedFile';
// TODO T21875029 ../../relay-runtime/util/RelayConcreteNode
import type {GeneratedNode} from 'RelayConcreteNode';
import type {
  CompiledDocumentMap,
  CompilerTransforms,
  FileWriterInterface,
  Reporter,
} from 'graphql-compiler';
import type {DocumentNode, GraphQLSchema} from 'graphql';

const {isOperationDefinitionAST} = SchemaUtils;

export type GenerateExtraFiles = (
  getOutputDirectory: (path?: string) => CodegenDirectory,
  compilerContext: CompilerContext,
  getGeneratedDirectory: (definitionName: string) => CodegenDirectory,
) => void;

export type WriterConfig = {
  baseDir: string,
  compilerTransforms: CompilerTransforms,
  customScalars: ScalarTypeMapping,
  formatModule: FormatModule,
  generateExtraFiles?: GenerateExtraFiles,
  inputFieldWhiteListForFlow: Array<string>,
  outputDir?: string,
  persistQuery?: (text: string) => Promise<string>,
  platform?: string,
  relayRuntimeModule?: string,
  schemaExtensions: Array<string>,
  useHaste: boolean,
  // Haste style module that exports flow types for GraphQL enums.
  // TODO(T22422153) support non-haste environments
  enumsHasteModule?: string,
};

class RelayFileWriter implements FileWriterInterface {
  _onlyValidate: boolean;
  _config: WriterConfig;
  _baseSchema: GraphQLSchema;
  _baseDocuments: ImmutableMap<string, DocumentNode>;
  _documents: ImmutableMap<string, DocumentNode>;
  _reporter: Reporter;

  constructor({
    config,
    onlyValidate,
    baseDocuments,
    documents,
    schema,
    reporter,
  }: {
    config: WriterConfig,
    onlyValidate: boolean,
    baseDocuments: ImmutableMap<string, DocumentNode>,
    documents: ImmutableMap<string, DocumentNode>,
    schema: GraphQLSchema,
    reporter: Reporter,
  }) {
    this._baseDocuments = baseDocuments || ImmutableMap();
    this._baseSchema = schema;
    this._config = config;
    this._documents = documents;
    this._onlyValidate = onlyValidate;
    this._reporter = reporter;

    validateConfig(this._config);
  }

  writeAll(): Promise<Map<string, CodegenDirectory>> {
    return Profiler.asyncContext('RelayFileWriter.writeAll', async () => {
      // Can't convert to IR unless the schema already has Relay-local extensions
      const transformedSchema = ASTConvert.transformASTSchema(
        this._baseSchema,
        this._config.schemaExtensions,
      );
      const extendedSchema = ASTConvert.extendASTSchema(
        transformedSchema,
        this._baseDocuments
          .merge(this._documents)
          .valueSeq()
          .toArray(),
      );

      // Build a context from all the documents
      const baseDefinitionNames = new Set();
      this._baseDocuments.forEach(doc => {
        doc.definitions.forEach(def => {
          if (isOperationDefinitionAST(def) && def.name) {
            baseDefinitionNames.add(def.name.value);
          }
        });
      });
      const definitionsMeta = new Map();
      const getDefinitionMeta = (definitionName: string) => {
        const definitionMeta = definitionsMeta.get(definitionName);
        invariant(
          definitionMeta,
          'RelayFileWriter: Could not determine source for definition: `%s`.',
          definitionName,
        );
        return definitionMeta;
      };
      const allOutputDirectories: Map<string, CodegenDirectory> = new Map();
      const addCodegenDir = dirPath => {
        const codegenDir = new CodegenDirectory(dirPath, {
          onlyValidate: this._onlyValidate,
        });
        allOutputDirectories.set(dirPath, codegenDir);
        return codegenDir;
      };

      let configOutputDirectory;
      if (this._config.outputDir) {
        configOutputDirectory = addCodegenDir(this._config.outputDir);
      }

      this._documents.forEach((doc, filePath) => {
        doc.definitions.forEach(def => {
          if (def.name) {
            definitionsMeta.set(def.name.value, {
              dir: path.join(this._config.baseDir, path.dirname(filePath)),
              ast: def,
            });
          }
        });
      });

      const definitions = ASTConvert.convertASTDocumentsWithBase(
        extendedSchema,
        this._baseDocuments.valueSeq().toArray(),
        this._documents.valueSeq().toArray(),
        // Verify using local and global rules, can run global verifications here
        // because all files are processed together
        [...RelayValidator.LOCAL_RULES, ...RelayValidator.GLOBAL_RULES],
        RelayParser.transform.bind(RelayParser),
      );<commit_msg>Pull `UnboundedConnectionChecher` validation rule into Relay<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @providesModule RelayFileWriter
 * @flow
 * @format
 */

'use strict';

const RelayCompiler = require('../RelayCompiler');
const RelayFlowGenerator = require('../core/RelayFlowGenerator');
const RelayParser = require('../core/RelayParser');
const RelayValidator = require('../core/RelayValidator');

const crypto = require('crypto');
const graphql = require('graphql');
const invariant = require('invariant');
const path = require('path');
const writeRelayGeneratedFile = require('./writeRelayGeneratedFile');

const {generate} = require('../core/RelayCodeGenerator');
const {
  ASTConvert,
  CodegenDirectory,
  CompilerContext,
  Profiler,
  SchemaUtils,
} = require('graphql-compiler');
const {Map: ImmutableMap} = require('immutable');

import type {ScalarTypeMapping} from '../core/RelayFlowTypeTransformers';
import type {FormatModule} from './writeRelayGeneratedFile';
// TODO T21875029 ../../relay-runtime/util/RelayConcreteNode
import type {GeneratedNode} from 'RelayConcreteNode';
import type {
  CompiledDocumentMap,
  CompilerTransforms,
  FileWriterInterface,
  Reporter,
} from 'graphql-compiler';
import type {DocumentNode, GraphQLSchema, ValidationContext} from 'graphql';

const {isOperationDefinitionAST} = SchemaUtils;

export type GenerateExtraFiles = (
  getOutputDirectory: (path?: string) => CodegenDirectory,
  compilerContext: CompilerContext,
  getGeneratedDirectory: (definitionName: string) => CodegenDirectory,
) => void;

export type ValidationRule = (context: ValidationContext) => any;

export type WriterConfig = {
  baseDir: string,
  compilerTransforms: CompilerTransforms,
  customScalars: ScalarTypeMapping,
  formatModule: FormatModule,
  generateExtraFiles?: GenerateExtraFiles,
  inputFieldWhiteListForFlow: Array<string>,
  outputDir?: string,
  persistQuery?: (text: string) => Promise<string>,
  platform?: string,
  relayRuntimeModule?: string,
  schemaExtensions: Array<string>,
  useHaste: boolean,
  // Haste style module that exports flow types for GraphQL enums.
  // TODO(T22422153) support non-haste environments
  enumsHasteModule?: string,
  validationRules?: {
    GLOBAL_RULES?: Array<ValidationRule>,
    LOCAL_RULES?: Array<ValidationRule>,
  },
};

class RelayFileWriter implements FileWriterInterface {
  _onlyValidate: boolean;
  _config: WriterConfig;
  _baseSchema: GraphQLSchema;
  _baseDocuments: ImmutableMap<string, DocumentNode>;
  _documents: ImmutableMap<string, DocumentNode>;
  _reporter: Reporter;

  constructor({
    config,
    onlyValidate,
    baseDocuments,
    documents,
    schema,
    reporter,
  }: {
    config: WriterConfig,
    onlyValidate: boolean,
    baseDocuments: ImmutableMap<string, DocumentNode>,
    documents: ImmutableMap<string, DocumentNode>,
    schema: GraphQLSchema,
    reporter: Reporter,
  }) {
    this._baseDocuments = baseDocuments || ImmutableMap();
    this._baseSchema = schema;
    this._config = config;
    this._documents = documents;
    this._onlyValidate = onlyValidate;
    this._reporter = reporter;

    validateConfig(this._config);
  }

  writeAll(): Promise<Map<string, CodegenDirectory>> {
    return Profiler.asyncContext('RelayFileWriter.writeAll', async () => {
      // Can't convert to IR unless the schema already has Relay-local extensions
      const transformedSchema = ASTConvert.transformASTSchema(
        this._baseSchema,
        this._config.schemaExtensions,
      );
      const extendedSchema = ASTConvert.extendASTSchema(
        transformedSchema,
        this._baseDocuments
          .merge(this._documents)
          .valueSeq()
          .toArray(),
      );

      // Build a context from all the documents
      const baseDefinitionNames = new Set();
      this._baseDocuments.forEach(doc => {
        doc.definitions.forEach(def => {
          if (isOperationDefinitionAST(def) && def.name) {
            baseDefinitionNames.add(def.name.value);
          }
        });
      });
      const definitionsMeta = new Map();
      const getDefinitionMeta = (definitionName: string) => {
        const definitionMeta = definitionsMeta.get(definitionName);
        invariant(
          definitionMeta,
          'RelayFileWriter: Could not determine source for definition: `%s`.',
          definitionName,
        );
        return definitionMeta;
      };
      const allOutputDirectories: Map<string, CodegenDirectory> = new Map();
      const addCodegenDir = dirPath => {
        const codegenDir = new CodegenDirectory(dirPath, {
          onlyValidate: this._onlyValidate,
        });
        allOutputDirectories.set(dirPath, codegenDir);
        return codegenDir;
      };

      let configOutputDirectory;
      if (this._config.outputDir) {
        configOutputDirectory = addCodegenDir(this._config.outputDir);
      }

      this._documents.forEach((doc, filePath) => {
        doc.definitions.forEach(def => {
          if (def.name) {
            definitionsMeta.set(def.name.value, {
              dir: path.join(this._config.baseDir, path.dirname(filePath)),
              ast: def,
            });
          }
        });
      });

      // Verify using local and global rules, can run global verifications here
      // because all files are processed together
      let validationRules = [
        ...RelayValidator.LOCAL_RULES,
        ...RelayValidator.GLOBAL_RULES,
      ];
      const customizedValidationRules = this._config.validationRules;
      if (customizedValidationRules) {
        validationRules = [
          ...validationRules,
          ...(customizedValidationRules.LOCAL_RULES || []),
          ...(customizedValidationRules.GLOBAL_RULES || []),
        ];
      }

      const definitions = ASTConvert.convertASTDocumentsWithBase(
        extendedSchema,
        this._baseDocuments.valueSeq().toArray(),
        this._documents.valueSeq().toArray(),
        validationRules,
        RelayParser.transform.bind(RelayParser),
      );
{'fraction_non_alphanumeric': 0.1081311196253725, 'fraction_numerical': 0.0034057045551298426, 'mean_word_length': 3.3763040238450075, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 48, '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': '4734034', 'n_tokens_mistral': 3243, 'n_tokens_neox': 3176, 'n_words': 739}