Giter Site home page Giter Site logo

danielnixon / eslint-plugin-total-functions Goto Github PK

View Code? Open in Web Editor NEW
87.0 87.0 4.0 2.84 MB

An ESLint plugin to enforce the use of total functions (and prevent the use of partial functions) in TypeScript.

License: MIT License

JavaScript 1.06% TypeScript 98.94%
eslint eslint-plugin functional-programming partial-functions type-safety typescript

eslint-plugin-total-functions's People

Contributors

danielnixon avatar dependabot-preview[bot] avatar dependabot[bot] avatar haolinj avatar renovate-bot avatar renovate[bot] avatar willheslam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

eslint-plugin-total-functions's Issues

[no-unsafe-subscript] doesn't catch some cases due to definite assignment analysis

Observe this annoying case:

const arr: readonly number[] = [];
const foo: number | undefined = arr[0]; // NOT flagged by no-unsafe-subscript
foo.toString(); // compiles; explodes at runtime

This is caused by definite assignment analysis. Even though foo has an explicit type annotation of number | undefined, TypeScript determines that foo cannot be undefined because it is "definitely assigned". toString() then explodes at runtime.

Our no-unsafe-subscript rule currently doesn't flag const foo: number | undefined = arr[0] because the contextual type of the expression arr[0] is number | undefined (the type of the value it's being assigned to).

This demonstrates that we can't assume the array access is safe even when the contextual type contains undefined -- at least for assignment expressions, which as far as I can tell are the only place that definite assignment analysis applies.

We will have to update no-unsafe-subscript to either ignore the contextual type in all cases (which would cause lots of pain and what amount to false positives) or only in assignment expressions (if possible).

🤦

Optional Array Access - Should be Valid

The primary way I would like to access an array value is with the ?. (optional chain operator) to access a value from an array:

        const arr: ReadonlyArray<number> = [];
        const foo = arr?.[0]; 
        // Type is number | undefined;

This is essentially the same as:

        const foo = arr[0] as number | undefined;

So this should be a valid pattern, not an invalid pattern.

From: https://github.com/danielnixon/eslint-plugin-total-functions/blob/master/src/rules/no-array-subscript.test.ts

In the invalid patterns:

// optional member array access
    {
      filename: "file.ts",
      code: `
        const arr: ReadonlyArray<number> = [];
        const foo = arr?.[0];
      `,
      errors: [
        {
          messageId: "errorStringGeneric",
          type: AST_NODE_TYPES.OptionalMemberExpression,
        },
      ],
    },

Am I missing something?

TSX test cases

... and any relaxation of the rules to allow safe usages required in response

total-functions/no-array-destructuring false positive with string literals

The code that reproduces the error:

interface Response {
  'error-code': number
}

function getError(
  { 'error-code': errorCode }: Response // <-- total-functions/no-array-destructuring
): string {
  return errorCode.toString()
}

There should be no total-functions/no-array-destructuring in this code.
For normal properties (not quoted), everything works fine.

I am using the library version:

Eslint config:

extends: [
  ...,
  'plugin:total-functions/recommended',
],
plugins: [
  ...,
  'total-functions'
]

Without rewriting the rules for eslint-plugin-total-functions.

`as` can lead to surprise mutation

Even with both the rules no-unsafe-assignment and no-unsafe-type-assertion enabled, this isn't complained about:

type MutableA = { a: string };
type ReadonlyA = { readonly a: string };

const ro: ReadonlyA = { a: "" };
const mut = ro as MutableA; // This shouldn't be allowed

It's not immediately clear which of those two rules should catch this, but at least one of them should.

Rename rules

no-array-subscript => no-unsafe-subscript
no-array-destructuring => no-unsafe-destructuring

(because they also cover objects and now only ban unsafe cases)

TypeScript 4 support

Need to update no-unsafe-assignment to cover the new short-circuiting assignment operators.

Allow array subscript access if return value is immediately assigned to a type that includes undefined

This would allow something like this without complaining:

const last: <A>(array: ReadonlyArray<A>) => A | undefined = (a) => a[a.length - 1];

These are cases where the partiality isn't really observable, similar to the ignoreImmediateMutation option in https://github.com/jonaskello/eslint-plugin-functional/blob/master/docs/rules/immutable-data.md#options

We probably want an option to disable this too.

Allow type widening in no-unsafe-type-assertion

Type assertions can be used to widen types. The no-unsafe-type-assertion rule currently flags those usages. It probably shouldn't.

E.g. these should pass the rule:

const foo = "foo" as string;
const bar = 1 as number;

false positive 'total-functions/no-unsafe-type-assertion' rule is disabled but never reported eslint-comments/no-unused-disable

public getMessages(locale?: Locale): IntlMessages {
    if (locale !== undefined) {
      const messages = this.messages.get(locale)

      if (messages !== undefined) {
        return messages
      }
    }

    const messages = this.messages.get(this.locale)

    if (messages !== undefined) {
      return messages
    }

    // eslint-disable-next-line total-functions/no-unsafe-type-assertion
    return {} as IntlMessages
  }

Error "'total-functions/no-unsafe-type-assertion' rule is disabled but never reported" doesn't show up in vscode, but shows up in terminal

Basically I need to return empty object at the end, so it has same type.

Support noUncheckedIndexedAccess

This is a tracking ticket for microsoft/TypeScript#39560.

When that lands, we will need to:

  1. Probably bail out of the no-unsafe-subscript rule early if that compiler flag is on (unless there are any remaining problematic cases). Or deprecate/remove this rule entirely?
  2. Ditto for no-unsafe-destructuring.
  3. Update the require-strict-mode rule to also check for this flag and complain if it isn't turned on. This assumes we'll have to enable noUncheckedIndexedAccess separately from strict, otherwise this point is just subsumed by #73.

New rule: unsafe assignment / declaration / return / call

Prevent this:

type A = { val: string; };
type B = { val: string | number; };

const a: A = { val: "string" };
const b: B = a;

b.val = 3;

console.log(a.val.toUpperCase()); // throws

And this:

interface MutableValue<T> {
    value: T;
}

interface ImmutableValue<T> {
    readonly value: T;
}

let i: ImmutableValue<string> = { value: "hi" };
i.value = "Excellent, I can't change it"; // compile-time error

let m: MutableValue<string> = i;
m.value = "Oh dear, I can change it";

see microsoft/TypeScript#14150 and microsoft/TypeScript#13347

Cannot read property 'isUnion' of undefined

I receive an error from the no-array-destructuring rule at this line:
const { items: [{ phone }] } = await response.json();

The stack trace is:

[Error - 1:43:28 AM] TypeError: Cannot read property 'isUnion' of undefined
    at ArrayPattern (node_modules/eslint-plugin-total-functions/dist/rules/no-array-destructuring.js:44:51)
    at node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (node_modules/eslint/lib/linter/node-event-generator.js:254:26)
    at NodeEventGenerator.applySelectors (node_modules/eslint/lib/linter/node-event-generator.js:283:22)
    at NodeEventGenerator.enterNode (node_modules/eslint/lib/linter/node-event-generator.js:297:14)
    at CodePathAnalyzer.enterNode (node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js:635:23)
    at node_modules/eslint/lib/linter/linter.js:949:32
    at Array.forEach (<anonymous>)```

Object indexing false positive

I'm wondering why the following example errors:

const periods = {
    oneDay: () => subDays(Date.now(), 1).valueOf(),
    oneWeek: () => subWeeks(Date.now(), 1).valueOf(),
    twoWeeks: () => subWeeks(Date.now(), 2).valueOf(),
    oneMonth: () => subMonths(Date.now(), 1).valueOf(),
    sixMonths: () => subMonths(Date.now(), 6).valueOf()
};
const period: keyof typeof periods = 'oneDay';
periods[period];

Great rule by the way, picking up lots of bugs.

New rule: Optional properties lead to unsoundness

This is unsettling:

type Foo = { readonly foo: string };
type Bar = { readonly foo: string, readonly bar?: () => unknown };
// This won't compile
// const foo: Foo = { foo: "foo", bar: "bar" };
// But this will
const thing = { foo: "foo", bar: "bar" };
const foo: Foo = thing;
// Uh oh...
const bar: Bar = foo;
if (bar.bar !== undefined) {
    // Runtime explosion
    // [ERR]: Executed JavaScript Failed: 
    // [ERR]: bar.bar is not a function 
    bar.bar();
}

It’s hard to point at precisely which line is “wrong”.

We may want a new rule that forbids:

const foo: Foo = thing;

We can think of it as taking the tsc error reported by

const foo: Foo = { foo: "foo", bar: "bar" };

and expanding its scope beyond just object literals.

New rule: ban unsafe type assertions

These shouldn't be flagged:

const foo = "foo" as unknown; // safe
const foo = "foo" as const; // safe
const arr = ["foo"];
const foo = arr[0] as string | undefined; // safe

But things like this should be:

type Foo = {
  readonly bar: number;
  readonly bas: string;
};

const foo = {} as Foo;

foo.bar.toString(); // Explodes at runtime

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.