private _optionalValue()

in src/assembler.ts [2139:2306]


  private _optionalValue(type: ts.Type, declaration: ts.Node | undefined, purpose: TypeUseKind): spec.OptionalValue {
    const isThisType = _isThisType(type, this._typeChecker, declaration?.parent);

    if (type.isLiteral() && _isEnumLike(type)) {
      type = this._typeChecker.getBaseTypeOfLiteralType(type);
    } else {
      type = this._typeChecker.getApparentType(type);
    }

    const primitiveType = _tryMakePrimitiveType.call(this);
    if (primitiveType) {
      return { type: primitiveType };
    }

    if (type.isUnion() && !_isEnumLike(type)) {
      return _unionType.call(this);
    }

    if (!type.symbol) {
      this._diagnostics.push(JsiiDiagnostic.JSII_1001_TYPE_HAS_NO_SYMBOL.create(declaration));
      return { type: spec.CANONICAL_ANY };
    }

    if (type.symbol.name === 'Array') {
      return { type: _arrayType.call(this) };
    }

    if (type.symbol.name === '__type' && type.symbol.members) {
      return { type: _mapType.call(this) };
    }

    if (type.symbol.escapedName === 'Promise') {
      const typeRef = type as ts.TypeReference;
      if (!typeRef.typeArguments || typeRef.typeArguments.length !== 1) {
        this._diagnostics.push(JsiiDiagnostic.JSII_1002_UNSPECIFIED_PROMISE.create(declaration));
        return { type: spec.CANONICAL_ANY };
      }
      return {
        type: this._typeReference(typeRef.typeArguments[0], declaration, purpose),
      };
    }

    const fqn = this._getFQN(type, declaration, purpose, isThisType);
    if (fqn == null) {
      this._diagnostics.push(
        JsiiDiagnostic.JSII_9997_UNKNOWN_ERROR.create(declaration, new Error('Could not determine FQN')),
      );
      return { type: { fqn: '' } };
    }

    return {
      type: { fqn },
    };

    function _arrayType(this: Assembler): spec.CollectionTypeReference {
      const typeRef = type as ts.TypeReference;
      let elementtype: spec.TypeReference;

      if (typeRef.typeArguments?.length === 1) {
        elementtype = this._typeReference(typeRef.typeArguments[0], declaration, 'list element type');
      } else {
        const count = typeRef.typeArguments ? typeRef.typeArguments.length : 'none';
        this._diagnostics.push(
          JsiiDiagnostic.JSII_1003_UNSUPPORTED_TYPE.create(
            declaration,
            `Array references must have exactly one type argument (found ${count})`,
          ),
        );
        elementtype = spec.CANONICAL_ANY;
      }

      return {
        collection: {
          elementtype,
          kind: spec.CollectionKind.Array,
        },
      };
    }

    function _mapType(this: Assembler): spec.CollectionTypeReference {
      let elementtype: spec.TypeReference;
      const objectType = type.getStringIndexType();
      if (objectType) {
        elementtype = this._typeReference(objectType, declaration, 'map element type');
      } else {
        const typeDecl = type.symbol.declarations?.[0];
        if (
          typeDecl != null &&
          ts.isTypeLiteralNode(typeDecl) &&
          typeDecl.members.length == 1 &&
          ts.isIndexSignatureDeclaration(typeDecl.members[0]) &&
          typeDecl.members[0].parameters[0].type != null &&
          ts.isTemplateLiteralTypeNode(typeDecl.members[0].parameters[0].type)
        ) {
          const indexTypeNode = typeDecl.members[0].type;
          const indexType = this._typeChecker.getTypeFromTypeNode(indexTypeNode);
          elementtype = this._typeReference(indexType, indexTypeNode, 'map element type');
        } else {
          this._diagnostics.push(
            JsiiDiagnostic.JSII_1003_UNSUPPORTED_TYPE.create(
              declaration,
              'Only string-indexed map types are supported',
            ),
          );
          elementtype = spec.CANONICAL_ANY;
        }
      }
      return {
        collection: {
          elementtype,
          kind: spec.CollectionKind.Map,
        },
      };
    }

    function _tryMakePrimitiveType(this: Assembler): spec.PrimitiveTypeReference | undefined {
      if (!type.symbol) {
        if (type.flags & ts.TypeFlags.Object) {
          if (isTupleType(type as ts.ObjectType)) {
            this._diagnostics.push(
              JsiiDiagnostic.JSII_1999_UNSUPPORTED.create(declaration, { what: 'Tuple types', alternative: 'arrays' }),
            );
          }

          return { primitive: spec.PrimitiveType.Json };
        }
        if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
          return spec.CANONICAL_ANY;
        }
      } else if (
        type.symbol.valueDeclaration &&
        isUnder(type.symbol.valueDeclaration.getSourceFile().fileName, this.stdlib)
      ) {
        switch (type.symbol.name) {
          case 'Boolean':
            return { primitive: spec.PrimitiveType.Boolean };
          case 'Date':
            return { primitive: spec.PrimitiveType.Date };
          case 'Number':
            return { primitive: spec.PrimitiveType.Number };
          case 'String':
            return { primitive: spec.PrimitiveType.String };
        }
      }
      // Not a primitive type!
      return undefined;
    }

    function _unionType(this: Assembler): spec.OptionalValue {
      const types = new Array<spec.TypeReference>();
      let optional: boolean | undefined;

      for (const subType of (type as ts.UnionType).types) {
        if (subType.flags & ts.TypeFlags.Undefined) {
          optional = true;
          continue;
        }
        // eslint-disable-next-line no-await-in-loop
        const resolvedType = this._typeReference(subType, declaration, purpose);
        if (types.some((ref) => deepEqual(ref, resolvedType))) {
          continue;
        }
        types.push(resolvedType);
      }

      return types.length === 1 ? { optional, type: types[0] } : { optional, type: { union: { types } } };
    }
  }