name: "unicorn:prefer-prototype-methods:object-reflect"

query: |
  query {
    File {
      ast_node {
        ... on FnCallAST {
          callee {
            ... on DotProperty {
              accessed_property {
                name @filter(op: "=", value: ["$apply"])
              }
              called_on {
                ... on VarRef {
                  name @filter(op: "=", value: ["$reflect"])
                }
              }
            }
          }
          argument @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) {
            is_spread @filter(op: "=", value: ["$true"])
          }
          argument {
            index @filter(op: "=", value: ["$zero"])
            value {
              strip_parens {
                span {
                  start @tag(name: "start")
                }
              }
            }
          }
          span {
            str @filter(op: "not_has_substring", value: ["$questionmark"])
          }
        }
      }
      expression {
        span {
          start @filter(op: "=", value: ["%start"])
        }
        strip_parens {
          ... on ObjectLiteral {
            entry @fold @transform(op: "count") @filter(op: "=", value: ["$zero"])
            span_: span {
              start @output
              end @output
            }
          }
        }
      }
    }
  }

args:
  reflect: "Reflect"
  apply: "apply"
  zero: 0
  true: true
  questionmark: "?"

summary: Prefer Array.prototype.X.{bind,call,apply} over [].X
reason: When “borrowing” a method from Array or Object, it's clearer to get it from the prototype than from an instance.

tests:
  pass:
    - relative_path:
        - "index.ts"
      code: |
        const foo_1 = Array.prototype.push.apply(bar, elements);
    - relative_path:
        - "index.ts"
      code: |
        const foo_2 = Array.prototype.slice.call(bar);
    - relative_path:
        - "index.ts"
      code: |
        const foo_3 = Object.prototype.toString.call(bar);
    - relative_path:
        - "index.ts"
      code: |
        const foo_4 = Object.prototype.hasOwnProperty.call(bar, "property");
    - relative_path:
        - "index.ts"
      code: |
        const foo_5 = Object.prototype.propertyIsEnumerable.call(bar, "property");
    - relative_path:
        - "index.ts"
      code: |
        const foo_6 = "".charCodeAt.call(bar, 0);
    - relative_path:
        - "index.ts"
      code: |
        const foo_7 = [notEmpty].push.apply(bar, elements);
    - relative_path:
        - "index.ts"
      code: |
        const foo_8 = {notEmpty}.toString.call(bar);
    - relative_path:
        - "index.ts"
      code: |
        Array.prototype.forEach.call(foo, () => {})
    - relative_path:
        - "index.ts"
      code: |
        const push_9 = Array.prototype.push.bind(foo)
    - relative_path:
        - "index.ts"
      code: |
        const push_10 = [].push
    - relative_path:
        - "index.ts"
      code: |
        const {push} = []
    - relative_path:
        - "index.ts"
      code: |
        Math.max.apply(null, numbers)
    - relative_path:
        - "index.ts"
      code: |
        foo.apply(null, args)
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply(...[].slice)
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply(foo, [].slice)
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply(Math.max, Math, numbers)
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply()
    - relative_path:
        - "index.ts"
      code: |
        Reflect["apply"]([].slice, foo, [])
    - relative_path:
        - "index.ts"
      code: |
        NotReflect.apply([].slice, foo, [])
    - relative_path:
        - "index.ts"
      code: |
        Reflect.notApply([].slice, foo, [])
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply([]?.slice, foo, [])
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply(foo, [].bar, [].bar)
    - relative_path:
        - "index.ts"
      code: |
        class Foo {bar() { this.baz.bind(this) }}
    - relative_path:
        - "index.ts"
      code: |
        const a = {bar() { this.baz.bind(this) }}
    - relative_path:
        - "index.ts"
      code: |
        foo.bar.bind(foo)
    - relative_path:
        - "index.ts"
      code: |
        foo.bar.bind(bar)
    - relative_path:
        - "index.ts"
      code: |
        foo[{}].call(bar)
    - relative_path:
        - "index.ts"
      code: |
        Object.hasOwn(bar)
  fail:
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply({}[Symbol()], baz, []);
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply({}[Symbol("symbol description")], baz, []);
    - relative_path:
        - "index.ts"
      code: |
        Reflect.apply({}[Symbol("symbol description")], baz, []);
