'': function()

in src/rule-generated-flow-types.js [698:963]


      'Program:exit': function (_node) {
        useFragmentInstances.forEach(useFragmentInstance => {
          const fragmentName = useFragmentInstance.fragmentName;
          const hookName = useFragmentInstance.hookName;
          const node = useFragmentInstance.node;
          const foundImport = imports.some(importDeclaration => {
            const importedFromModuleName = importDeclaration.source.value;
            // `includes()` to allow a suffix like `.js` or path prefixes
            if (!importedFromModuleName.includes(fragmentName + '.graphql')) {
              return false;
            }
            // import type {...} from '...';
            if (importDeclaration.importKind === 'type') {
              return importDeclaration.specifiers.some(
                specifier =>
                  specifier.type === 'ImportSpecifier' &&
                  specifier.imported.name === fragmentName + '$key'
              );
            }
            // import {type xyz} from '...';
            if (importDeclaration.importKind === 'value') {
              return importDeclaration.specifiers.some(
                specifier =>
                  specifier.type === 'ImportSpecifier' &&
                  specifier.importKind === 'type' &&
                  specifier.imported.name === fragmentName + '$key'
              );
            }
            return false;
          });

          if (foundImport) {
            return;
          }

          // Check if the fragment ref that we're passing to the hook
          // comes from a previous useFragment (or variants) hook call.
          const fragmentRefArgName =
            node.arguments[1] != null ? node.arguments[1].name : null;
          const foundFragmentRefDeclaration = useFragmentInstances.some(
            _useFragmentInstance => {
              if (_useFragmentInstance === useFragmentInstance) {
                return false;
              }
              const variableDeclaratorNode = _useFragmentInstance.node.parent;
              if (
                !variableDeclaratorNode ||
                !variableDeclaratorNode.id ||
                !variableDeclaratorNode.id.type
              ) {
                return false;
              }
              if (variableDeclaratorNode.id.type === 'Identifier') {
                return (
                  fragmentRefArgName != null &&
                  variableDeclaratorNode.id.name === fragmentRefArgName
                );
              }
              if (
                variableDeclaratorNode.id.type === 'ObjectPattern' &&
                variableDeclaratorNode.id.properties != null
              ) {
                return variableDeclaratorNode.id.properties.some(prop => {
                  return (
                    fragmentRefArgName != null &&
                    prop &&
                    prop.value &&
                    prop.value.name === fragmentRefArgName
                  );
                });
              }
              return false;
            }
          );

          if (foundFragmentRefDeclaration) {
            return;
          }

          context.report({
            node: node,
            message:
              'The prop passed to {{hookName}}() should be typed with the ' +
              "type '{{name}}$key' imported from '{{name}}.graphql', " +
              'e.g.:\n' +
              '\n' +
              "  import type {{{name}}$key} from '{{name}}.graphql';",
            data: {
              name: fragmentName,
              hookName: hookName
            }
          });
        });
        expectedTypes.forEach(type => {
          const componentName = type.split('_')[0];
          const propName = type.split('_').slice(1).join('_');
          if (!componentName || !propName || !componentMap[componentName]) {
            // incorrect name, covered by graphql-naming/CallExpression
            return;
          }
          const Component = componentMap[componentName].Component;
          const propType = componentMap[componentName].propType;

          // resolve local type alias
          const importedPropType = imports.reduce((acc, node) => {
            if (node.specifiers) {
              const typeSpecifier = node.specifiers.find(specifier => {
                if (specifier.type !== 'ImportSpecifier') {
                  return false;
                }
                return specifier.imported.name === type;
              });
              if (typeSpecifier) {
                return typeSpecifier.local.name;
              }
            }
            return acc;
          }, type);

          const importFixRange = genImportFixRange(
            importedPropType,
            imports,
            requires
          );

          if (propType) {
            // There exists a prop typeAnnotation. Let's look at how it's
            // structured
            switch (propType.type) {
              case 'ObjectTypeAnnotation': {
                validateObjectTypeAnnotation(
                  context,
                  Component,
                  importedPropType,
                  propName,
                  propType,
                  importFixRange,
                  typeAliasMap
                );
                break;
              }
              case 'GenericTypeAnnotation': {
                const aliasedObjectType = extractReadOnlyType(
                  resolveTypeAlias(propType, typeAliasMap)
                );
                if (!aliasedObjectType) {
                  // The type Alias doesn't exist, is invalid, or is being
                  // imported. Can't do anything.
                  break;
                }
                switch (aliasedObjectType.type) {
                  case 'ObjectTypeAnnotation': {
                    validateObjectTypeAnnotation(
                      context,
                      Component,
                      importedPropType,
                      propName,
                      aliasedObjectType,
                      importFixRange,
                      typeAliasMap
                    );
                    break;
                  }
                  case 'IntersectionTypeAnnotation': {
                    const objectTypes = aliasedObjectType.types
                      .map(intersectedType => {
                        if (intersectedType.type === 'GenericTypeAnnotation') {
                          return extractReadOnlyType(
                            resolveTypeAlias(intersectedType, typeAliasMap)
                          );
                        }
                        if (intersectedType.type === 'ObjectTypeAnnotation') {
                          return intersectedType;
                        }
                      })
                      .filter(maybeObjectType => {
                        // GenericTypeAnnotation may not map to an object type
                        return (
                          maybeObjectType &&
                          maybeObjectType.type === 'ObjectTypeAnnotation'
                        );
                      });
                    if (!objectTypes.length) {
                      // The type Alias is likely being imported.
                      // Can't do anything.
                      break;
                    }
                    for (const objectType of objectTypes) {
                      const isValid = validateObjectTypeAnnotation(
                        context,
                        Component,
                        importedPropType,
                        propName,
                        objectType,
                        importFixRange,
                        typeAliasMap,
                        true // Return false if invalid instead of reporting
                      );
                      if (isValid) {
                        break;
                      }
                    }
                    // otherwise report an error at the first object
                    validateObjectTypeAnnotation(
                      context,
                      Component,
                      importedPropType,
                      propName,
                      objectTypes[0],
                      importFixRange,
                      typeAliasMap
                    );
                    break;
                  }
                }
                break;
              }
            }
          } else {
            context.report({
              message:
                'Component property `{{prop}}` expects to use the ' +
                'generated `{{type}}` flow type. See https://facebook.github.io/relay/docs/en/graphql-in-relay.html#importing-generated-definitions',
              data: {
                prop: propName,
                type: importedPropType
              },
              fix: options.fix
                ? fixer => {
                    const classBodyStart = Component.parent.body.body[0];
                    if (!classBodyStart) {
                      // HACK: There's nothing in the body. Let's not do anything
                      // When something is added to the body, we'll have a fix
                      return;
                    }
                    const aliasWhitespace = ' '.repeat(
                      Component.parent.loc.start.column
                    );
                    const propsWhitespace = ' '.repeat(
                      classBodyStart.loc.start.column
                    );
                    return [
                      genImportFixer(
                        fixer,
                        importFixRange,
                        importedPropType,
                        options.haste,
                        aliasWhitespace
                      ),
                      fixer.insertTextBefore(
                        Component.parent,
                        `type Props = {${propName}: ` +
                          `${importedPropType}};\n\n${aliasWhitespace}`
                      ),
                      fixer.insertTextBefore(
                        classBodyStart,
                        `props: Props;\n\n${propsWhitespace}`
                      )
                    ];
                  }
                : null,
              loc: Component.loc
            });
          }
        });
      }