## ContractCase Maintainer Documentation: How to add a new matcher

This describes the process to add in a new matcher for maintainers.
Adding a new matcher as a plugin will be a subset of these steps.

### Creating the types

First create the types in `entities/nodes/matchers/types.ts`. These describe
the raw data (mostly parameters if your matcher has any)

1.  All matchers much have a constant for the type of the matcher. The
    type must have an exported constant for its type. This is used to
    determine what type of matcher it is and to run the associated matching
    functions. For example:
    ```ts
    export const ARRAY_LENGTH_MATCHER_TYPE = 'ArrayLength' as const;
    ```
2.  Export a new interface that describes the actual matcher JSON. This is
    what will be written to the contract file, and generated by the matcher DSL.

    It must include `case:matcher:type`, set to the exact type constant string you created in the previous step.
    All parameter fields must be prefixed with `case:matcher:`. For example:

    ```ts
    export interface CoreArrayLengthMatcher {
      'case:matcher:type': typeof ARRAY_LENGTH_MATCHER_TYPE;
      'case:matcher:minLength': number;
      'case:matcher:maxLength': number;
    }
    ```

    If your matcher modifies the context, add fields prefixed with
    `case:context:` - these are automatically picked up by ContractCase and rolled
    into the context before this matcher is invoked (and passed down to any child matchers).

3.  Add these new types to both `AnyCaseNodeType` and `AnyCaseMatcher`:

    ```ts
    export type AnyCaseNodeType =
      // ...etc
      typeof ARRAY_LENGTH_MATCHER_TYPE;

    export type AnyCaseMatcher =
      // ...etc
      CoreArrayLengthMatcher;
    ```

### Creating the MatcherExecutor

Next, we will add the behaviour of the matcher, both for matching, and for stripping the matchers.

1. Add a new `MatcherExecutor<typeof YOUR_NEW_TYPE>` in an appropriate place in `diffmatch`. For example:

   ```ts
   const strip: StripMatcherFn<typeof ARRAY_LENGTH_MATCHER_TYPE> = (
       matcher: CoreArrayLengthMatcher,
       matchContext: MatchContext
   ): AnyData => // implement the strip matcher function here


   const check: CheckMatchFn<typeof ARRAY_LENGTH_MATCHER_TYPE> = (
       matcher: CoreArrayLengthMatcher,
       matchContext: MatchContext,
       actual: unknown
   ): Promise<MatchResult> | MatchResult => // Implement your check here

   export const ArrayLengthExecutor: MatcherExecutor<
       typeof ARRAY_LENGTH_MATCHER_TYPE
   > = { check, strip };
   ```

   If you need to recurse further into any children of your matchers, use
   `matchContext.descendAndCheck()` or `matchContext.descendAndStrip()` as
   appropriate. See the existing matchers for examples.

   If your matcher doesn't have enough context to strip matchers (eg, for
   auxillery matchers designed to be used with `and()`), then throw a `new StripUnsupportedError(matcher, matchContext)` inside your implementation of `strip()`.

   Note that matcher executors are not allowed to call other matcher executors -
   only `descendAndCheck()`. If you need to combine matchers, do it at the DSL
   layer with `and()`

2. Add the matcher executor to `MatcherExecutors.ts`:
   ```ts
   export const MatcherExecutors: {
     [T in AnyCaseNodeType]: MatcherExecutor<T>;
   } = {
     // ...etc
     [ARRAY_LENGTH_MATCHER_TYPE]: ArrayLengthExecutor,
   };
   ```

### Create the matcher DSL

Create a DSL function that creates your matcher type, for example:

```ts
/**
 * Everything inside this matcher will be matched exactly, unless overridden with an `any*` matcher
 *
 * Use this to switch out of `shapedLike` and back to the default exact matching.
 *
 * @param content What
 */
export const exactlyLike = (
  content: AnyCaseNodeOrData
): CoreCascadingMatcher => ({
  'case:matcher:type': CASCADING_CONTEXT_MATCHER_TYPE,
  'case:matcher:child': content,
  'case:context:matchBy': 'exact',
});
```

If your matcher needs some double checking or additional processing (eg invoking
other matchers to make a composite matcher), do it in the DSL layer. The matcher
functions in the entities layer are intended to be data only.

### Test it

Add or create additional tests at the top level (see `index.*.spec.ts` for examples)
