Repository: angular/angular
File: packages/compiler-cli/src/ngtsc/typecheck/src/ops/scope.ts (TypeScript)

Code before:
    } else {
      this.opQueue.push(
        new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs),
      );
    }
  }

  private appendDirectiveInputs(
    dir: TypeCheckableDirectiveMeta,
    node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
    dirMap: Map<TypeCheckableDirectiveMeta, number>,
    allDirectiveMatches: TypeCheckableDirectiveMeta[],
  ): void {
    const customFieldType = allDirectiveMatches.some(isFieldDirective)
      ? getCustomFieldDirectiveType(dir)
      : null;

    const directiveOp = this.getDirectiveOp(dir, node, customFieldType);
    const dirIndex = this.opQueue.push(directiveOp) - 1;
    dirMap.set(dir, dirIndex);

    if (
      isFieldDirective(dir) &&
      node instanceof TmplAstElement &&
      (node.name === 'input' || node.name === 'select' || node.name === 'textarea') &&
      allDirectiveMatches.every((meta) => {
        return getCustomFieldDirectiveType(meta) === null && !isControlValueAccessorLike(meta);
      })
    ) {
      this.opQueue.push(new TcbNativeFieldDirectiveTypeOp(this.tcb, this, node));
    }

    this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir, customFieldType));
  }

  private getDirectiveOp(
    dir: TypeCheckableDirectiveMeta,
    node: DirectiveOwner,
    customFieldType: CustomFieldType | null,
  ): TcbOp {
    const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;

    if (!dir.isGeneric) {
      // The most common case is that when a directive is not generic, we use the normal
      // `TcbNonDirectiveTypeOp`.
      return new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir);
    } else if (
      !requiresInlineTypeCtor(dirRef.node, this.tcb.env.reflector, this.tcb.env) ||
      this.tcb.env.config.useInlineTypeConstructors
    ) {
      // For generic directives, we use a type constructor to infer types. If a directive requires

Review comment (suggestion):
I was debating between this and disabling the native control type checking if there are _any_ directives (aside from `Field`) on the same element. I went with this approach, because the alternative seemed a bit aggressive.

Code after:
    } else {
      this.opQueue.push(
        new TcbUnclaimedOutputsOp(this.tcb, this, node, node.outputs, node.inputs, claimedOutputs),
      );
    }
  }

  private appendDirectiveInputs(
    dir: TypeCheckableDirectiveMeta,
    node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
    dirMap: Map<TypeCheckableDirectiveMeta, number>,
    allDirectiveMatches: TypeCheckableDirectiveMeta[],
  ): void {
    const customFieldType = allDirectiveMatches.some(isFieldDirective)
      ? getCustomFieldDirectiveType(dir)
      : null;

    const directiveOp = this.getDirectiveOp(dir, node, customFieldType);
    const dirIndex = this.opQueue.push(directiveOp) - 1;
    dirMap.set(dir, dirIndex);

    if (isNativeField(dir, node, allDirectiveMatches)) {
      this.opQueue.push(new TcbNativeFieldDirectiveTypeOp(this.tcb, this, node as TmplAstElement));
    }

    this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir, customFieldType));
  }

  private getDirectiveOp(
    dir: TypeCheckableDirectiveMeta,
    node: DirectiveOwner,
    customFieldType: CustomFieldType | null,
  ): TcbOp {
    const dirRef = dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>;

    if (!dir.isGeneric) {
      // The most common case is that when a directive is not generic, we use the normal
      // `TcbNonDirectiveTypeOp`.
      return new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir);
    } else if (
      !requiresInlineTypeCtor(dirRef.node, this.tcb.env.reflector, this.tcb.env) ||
      this.tcb.env.config.useInlineTypeConstructors
    ) {
      // For generic directives, we use a type constructor to infer types. If a directive requires
      // an inline type constructor, then inlining must be available to use the
      // `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below.
      return new TcbDirectiveCtorOp(this.tcb, this, node, dir, customFieldType);
    }

    // If inlining is not available, then we give up on inferring the generic params, and use
    // `any` type for the directive's generic parameters.