private async deduplicateComponent()

in packages/libs/deduplication/src/main.ts [264:381]


  private async deduplicateComponent(componentUid: string, type: string) {
    switch (type) {
      case "schemas":
      case "responses":
      case "parameters":
      case "examples":
      case "requestBodies":
      case "headers":
      case "links":
      case "callbacks":
      case "securitySchemes":
        if (!this.deduplicatedComponents[type].has(componentUid)) {
          if (!this.crawledComponents[type].has(componentUid)) {
            await YieldCPU();
            await this.crawlComponent(componentUid, type);
          }

          // Just try to deduplicate if it was not deduplicated while crawling another component.
          if (!this.deduplicatedComponents[type].has(componentUid)) {
            // deduplicate crawled component
            const xMsMetadata = "x-ms-metadata";
            const component = this.target.components[type][componentUid];

            // extract metadata to be merged
            let apiVersions = component[xMsMetadata].apiVersions;
            let filename = component[xMsMetadata].filename;
            let originalLocations = component[xMsMetadata].originalLocations;
            let name = component[xMsMetadata].name;

            // extract component properties excluding metadata
            const { "x-ms-metadata": metadataCurrent, ...filteredComponent } = component;

            // iterate over all the components of the same type of the component
            for (const { key: anotherComponentUid, value: anotherComponent } of visit(this.target.components[type])) {
              // ignore merge with itself && anotherComponent already deleted (i.e. undefined)
              if (anotherComponent !== undefined && componentUid !== anotherComponentUid) {
                // extract the another component's properties excluding metadata
                const { "x-ms-metadata": metadataSchema, ...filteredAnotherComponent } = anotherComponent;

                // TODO: Add more keys to ignore.
                const keysToIgnore: Array<string> = ["description", "x-ms-original", "x-ms-examples"];

                const namesMatch = this.deduplicateInlineModels
                  ? anotherComponent[xMsMetadata].name.replace(/\d*$/, "") ===
                      component[xMsMetadata].name.replace(/\d*$/, "") ||
                    (anotherComponent[xMsMetadata].name.indexOf("·") > -1 &&
                      component[xMsMetadata].name.indexOf("·") > -1)
                  : anotherComponent[xMsMetadata].name.replace(/\d*$/, "") ===
                    component[xMsMetadata].name.replace(/\d*$/, "");

                // const t1 = component['type'];
                // const t2 = anotherComponent['type'];
                // const typesAreSame = t1 === t2;
                // const isObjectSchema = t1 === 'object' || t1 === undefined;
                // const isStringSchemaWithFormat = !!(t1 === 'string' && component['format']);

                // (type === 'schemas' && t1 === t2 && (t1 === 'object' || (t1 === 'string' && component['format']) || t1 === undefined))
                // they should have the same name to be merged and they should be similar
                if (
                  namesMatch &&
                  // typesAreSame &&
                  // (isObjectSchema || isStringSchemaWithFormat) &&
                  areSimilar(filteredAnotherComponent, filteredComponent, ...keysToIgnore)
                ) {
                  // if the primary has a synthetic name, and the secondary doesn't use the secondary's name
                  if (name.indexOf("·") > 0 && anotherComponent[xMsMetadata].name.indexOf("·") === -1) {
                    name = anotherComponent[xMsMetadata].name;
                  }

                  // merge metadata
                  apiVersions = apiVersions.concat(anotherComponent[xMsMetadata].apiVersions);
                  filename = filename.concat(anotherComponent[xMsMetadata].filename);
                  originalLocations = originalLocations.concat(anotherComponent[xMsMetadata].originalLocations);

                  // the discriminator to take contents is the api version
                  const maxApiVersionComponent = maximum(component[xMsMetadata].apiVersions);
                  const maxApiVersionAnotherComponent = maximum(anotherComponent[xMsMetadata].apiVersions);
                  let uidComponentToDelete = anotherComponentUid;
                  if (
                    compareVersions(toSemver(maxApiVersionComponent), toSemver(maxApiVersionAnotherComponent)) === -1
                  ) {
                    // if the current component max api version is less than the another component, swap ids.
                    uidComponentToDelete = componentUid;
                    componentUid = anotherComponentUid;
                  }

                  // finish up
                  delete this.target.components[type][uidComponentToDelete];
                  this.refs[`#/components/${type}/${uidComponentToDelete}`] = `#/components/${type}/${componentUid}`;
                  this.updateRefs(this.target);
                  this.updateMappings(
                    `/components/${type}/${uidComponentToDelete}`,
                    `/components/${type}/${componentUid}`,
                  );
                  this.deduplicatedComponents[type].add(uidComponentToDelete);
                }
              }
            }

            this.target.components[type][componentUid][xMsMetadata] = {
              apiVersions: [...new Set([...apiVersions])],
              filename: [...new Set([...filename])],
              name,
              originalLocations: [...new Set([...originalLocations])],
            };

            this.deduplicatedComponents[type].add(componentUid);
          }
        }
        break;
      default:
        if (isExtensionKey(type)) {
          // Skip unknown extension type
          return;
        }
        throw new Error(`Unknown component type: '${type}'`);
    }
  }