<commit_before>import { watch, ref, Ref, isRef, reactive, computed, onMounted, toRefs, watchEffect } from 'vue';
import { validate } from './validate';
import { FormController, ValidationResult, MaybeReactive, FieldComposite, GenericValidateFunction } from './types';
import { createFlags, normalizeRules, extractLocators } from './utils';
import { normalizeEventValue } from './utils/events';
import { unwrap } from './utils/refs';

interface FieldOptions {
  value?: Ref<any>;
  immediate?: boolean;
  form?: FormController;
}

interface CompleteFieldOptions {
  value: Ref<any>;
  immediate?: boolean;
  form?: FormController;
}

type RuleExpression = MaybeReactive<string | Record<string, any> | GenericValidateFunction>;

export function useField(fieldName: MaybeReactive<string>, rules: RuleExpression, opts?: FieldOptions): FieldComposite {
  const { value, form, immediate } = useFieldOptions(opts);
  const { flags, errors, failedRules, onBlur, handleChange, reset, patch } = useValidationState(value);
  let schemaValidation: GenericValidateFunction;
  const normalizedRules = computed(() => {
    return schemaValidation || normalizeRules(unwrap(rules));
  });

  const validateField = async (): Promise<ValidationResult> => {
    flags.pending.value = true;
    const result = await validate(value.value, normalizedRules.value, {
      name: unwrap(fieldName),
      values: form?.values.value ?? {},
      names: form?.names.value ?? {},
    });

    patch(result);

    return result;
  };

  watch(value, validateField, {
    deep: true,
  });

  if (isRef(rules)) {
    watch(rules, validateField, {
      deep: true,
    });
  }

  onMounted(() => {
    validate(value.value, unwrap(rules)).then(result => {
      if (immediate) {
        patch(result);
        return;
      }

      // Initial silent validation.
      flags.valid.value = result.valid;
      flags.invalid.value = !result.valid;
    });
  });

  const errorMessage = computed(() => {
    return errors.value[0];
  });

  const field = {
    vid: fieldName,
    name: fieldName, // TODO: Custom field names
    value: value,
    ...flags,
    errors,
    errorMessage,
    failedRules,
    reset,
    validate: validateField,
    handleChange,
    onBlur,
    __setRules(fn: GenericValidateFunction) {
      schemaValidation = fn;
    },
  };

  useFormController(field, normalizedRules, form);

  return field;
}

function useFieldOptions(opts: FieldOptions | undefined): CompleteFieldOptions {
  const defaults = () => ({
    value: ref(null),
    immediate: false,
    rules: '',
  });

  if (!opts) {
    return defaults();
  }

  return {
    ...defaults(),
    ...(opts ?? {}),
  };
}

function useValidationState(value: Ref<any>) {
  const errors: Ref<string[]> = ref([]);
  const { onBlur, reset: resetFlags, ...flags } = useFlags();
  const failedRules: Ref<Record<string, string>> = ref({});
  const initialValue = value.value;

  const handleChange = (e: Event) => {
    value.value = normalizeEventValue(e);
    flags.dirty.value = true;
    flags.pristine.value = false;
  };

  function patch(result: ValidationResult) {
    errors.value = result.errors;
    flags.changed.value = initialValue !== value.value;
    flags.valid.value = result.valid;
    flags.invalid.value = !result.valid;
    flags.validated.value = true;
    flags.pending.value = false;
    failedRules.value = result.failedRules;
  }

  const reset = () => {
    errors.value = [];
    failedRules.value = {};
    resetFlags();
  };

  return {
    flags,
    errors,
    failedRules,
    patch,
    reset,
    onBlur,
    handleChange,
  };
}

function useFormController(field: FieldComposite, rules: Ref<Record<string, any>>, form?: FormController) {
  if (!form) return;

  form.register(field);

  const dependencies = computed(() => {
    return Object.keys(rules.value).reduce((acc: string[], rule: string) => {
      const deps = extractLocators(rules.value[rule]).map((dep: any) => dep.__locatorRef);
      acc.push(...deps);

      return acc;
    }, []);
  });

  watchEffect(() => {
    if (!dependencies.value.length) {
      return;
    }

    dependencies.value.forEach(dep => {
      if (dep in form.values.value && field.validated.value) {
        field.validate();
      }
    });
  });
}

function useFlags() {
  const flags = reactive(createFlags());
  const passed = computed(() => {
    return flags.valid && flags.validated;
  });

  const failed = computed(() => {
    return flags.invalid && flags.validated;
  });

  const onBlur = () => {
    flags.touched = true;
    flags.untouched = false;
  };
  function reset() {
    const defaults = createFlags();
    Object.keys(flags).forEach((key: string) => {
      // Skip these, since they are computed anyways.
      if (key === 'passed' || key === 'failed') {
        return;
      }

      flags[key] = defaults[key];
    });
  }

  return {
    ...toRefs(flags),
    passed,
    failed,
    onBlur,
    reset,
  };
}
<commit_msg>refactor: minor code docs and refactoring for useField<commit_after>import { watch, ref, Ref, isRef, reactive, computed, onMounted, toRefs, watchEffect } from 'vue';
import { validate } from './validate';
import { FormController, ValidationResult, MaybeReactive, FieldComposite, GenericValidateFunction } from './types';
import { createFlags, normalizeRules, extractLocators } from './utils';
import { normalizeEventValue } from './utils/events';
import { unwrap } from './utils/refs';

interface FieldOptions {
  value: Ref<any>;
  immediate?: boolean;
  form?: FormController;
}

type RuleExpression = MaybeReactive<string | Record<string, any> | GenericValidateFunction>;

/**
 * Creates a field composite.
 */
export function useField(
  fieldName: MaybeReactive<string>,
  rules: RuleExpression,
  opts?: Partial<FieldOptions>
): FieldComposite {
  const { value, form, immediate } = normalizeOptions(opts);
  const { flags, errors, failedRules, onBlur, handleChange, reset, patch } = useValidationState(value);
  let schemaValidation: GenericValidateFunction;
  const normalizedRules = computed(() => {
    return schemaValidation || normalizeRules(unwrap(rules));
  });

  const runValidation = async (): Promise<ValidationResult> => {
    flags.pending.value = true;
    const result = await validate(value.value, normalizedRules.value, {
      name: unwrap(fieldName),
      values: form?.values.value ?? {},
      names: form?.names.value ?? {},
    });

    patch(result);

    return result;
  };

  watch(value, runValidation, {
    deep: true,
  });

  if (isRef(rules)) {
    watch(rules, runValidation, {
      deep: true,
    });
  }

  onMounted(() => {
    validate(value.value, unwrap(rules)).then(result => {
      if (immediate) {
        patch(result);
        return;
      }

      // Initial silent validation.
      flags.valid.value = result.valid;
      flags.invalid.value = !result.valid;
    });
  });

  const errorMessage = computed(() => {
    return errors.value[0];
  });

  const field = {
    vid: fieldName,
    name: fieldName, // TODO: Custom field names
    value: value,
    ...flags,
    errors,
    errorMessage,
    failedRules,
    reset,
    validate: runValidation,
    handleChange,
    onBlur,
    __setRules(fn: GenericValidateFunction) {
      schemaValidation = fn;
    },
  };

  useFormController(field, normalizedRules, form);

  return field;
}

/**
 * Normalizes partial field options to include the full
 */
function normalizeOptions(opts: Partial<FieldOptions> | undefined): FieldOptions {
  const defaults = () => ({
    value: ref(null),
    immediate: false,
    rules: '',
  });

  if (!opts) {
    return defaults();
  }

  return {
    ...defaults(),
    ...(opts ?? {}),
  };
}

/**
 * Manages the validation state of a field.
 */
function useValidationState(value: Ref<any>) {
  const errors: Ref<string[]> = ref([]);
  const { onBlur, reset: resetFlags, ...flags } = useMeta();
  const failedRules: Ref<Record<string, string>> = ref({});
  const initialValue = value.value;

  // Common input/change event handler
  const handleChange = (e: Event) => {
    value.value = normalizeEventValue(e);
    flags.dirty.value = true;
    flags.pristine.value = false;
  };

  // Updates the validation state with the validation result
  function patch(result: ValidationResult) {
    errors.value = result.errors;
    flags.changed.value = initialValue !== value.value;
    flags.valid.value = result.valid;
    flags.invalid.value = !result.valid;
    flags.validated.value = true;
    flags.pending.value = false;
    failedRules.value = result.failedRules;
  }

  // Resets the validation state
  const reset = () => {
    errors.value = [];
    failedRules.value = {};
    resetFlags();
  };

  return {
    flags,
    errors,
    failedRules,
    patch,
    reset,
    onBlur,
    handleChange,
  };
}

/**
 * Associated fields with forms and watches any cross-field validation dependencies.
 */
function useFormController(field: FieldComposite, rules: Ref<Record<string, any>>, form?: FormController) {
  if (!form) return;

  // associate the field with the given form
  form.register(field);

  // extract cross-field dependencies in a computed prop
  const dependencies = computed(() => {
    return Object.keys(rules.value).reduce((acc: string[], rule: string) => {
      const deps = extractLocators(rules.value[rule]).map((dep: any) => dep.__locatorRef);
      acc.push(...deps);

      return acc;
    }, []);
  });

  // Adds a watcher that runs the validation whenever field dependencies change
  watchEffect(() => {
    // Skip if no dependencies
    if (!dependencies.value.length) {
      return;
    }

    // For each dependent field, validate it if it was validated before
    dependencies.value.forEach(dep => {
      if (dep in form.values.value && field.validated.value) {
        field.validate();
      }
    });
  });
}

/**
 * Exposes meta flags state and some associated actions with them.
 */
function useMeta() {
  const flags = reactive(createFlags());
  const passed = computed(() => {
    return flags.valid && flags.validated;
  });

  const failed = computed(() => {
    return flags.invalid && flags.validated;
  });

  /**
   * Handles common onBlur meta update
   */
  const onBlur = () => {
    flags.touched = true;
    flags.untouched = false;
  };

  /**
   * Resets the flag state
   */
  function reset() {
    const defaults = createFlags();
    Object.keys(flags).forEach((key: string) => {
      // Skip these, since they are computed anyways
      if (key === 'passed' || key === 'failed') {
        return;
      }

      flags[key] = defaults[key];
    });
  }

  return {
    ...toRefs(flags),
    passed,
    failed,
    onBlur,
    reset,
  };
}

{'fraction_non_alphanumeric': 0.14405438122730235, 'fraction_numerical': 0.0001862370798025887, 'mean_word_length': 3.072810011376564, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3491003', 'n_tokens_mistral': 3228, 'n_tokens_neox': 3158, 'n_words': 788}