npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

intellipath

v0.0.1

Published

IntelliSense-like autocompletion features for string literals in TypeScript

Downloads

12

Readme

IntelliPath

IntelliSense-like autocompletion features for string literals in TypeScript

Latest Stable Version npm peer dependency version David License


Features

  • Check if a string can be used as a path for an instance of a type
  • Provide an easy-to-use generic type for your libraries
  • (Ab)use your IDE’s autocompletion to provide the best developer experience
  • Gradual typing means less resource usage
  • Does not compute every single path on every keystroke
  • Allows numeric indices to access array elements

Installation

# NPM
npm i -D intellipath

# Yarn
yarn add --dev intellipath

Usage

This module has one named export, IntelliPath. All the internal types are documented and exported as the default export object if you wish to use them.

By itself, IntelliPath is not that useful:

import { IntelliPath } from "intellipath";

type T = {
    a: {
        b: number;
        c: string[];
    };
    d: string;
};

type a = IntelliPath<T, "">;       // a = "" | "a" | "d"
type b = IntelliPath<T, "a">;      // b = "a" | "a.b" | "a.c"
type c = IntelliPath<T, "a.c.hi">; // c = "a.c" | "a.c.<index>"

Its true utility shines when you create a “feedback loop” between its string type argument, a function type parameter, and a function parameter:

import { IntelliPath } from "intellipath";

type Test = {
    nested: {
        result?: unknown;
        error?: {
            description: string;
            code: number;
        };
    };
    array: { one: 1; two: 2; three: 3 }[];
};

function get<P extends string>(path: IntelliPath<Test, P>) {}

get("")
//   ^ Place your cursor here and ^Space

Paste this code in a .ts file in your project and try out the autocompletion!

Caveats

  • Autocompletion is a bit finnicky, it works best when the string you’re trying to autocomplete is terminated.
    get("
    //   ^ Trying to autocomplete this will do weird things
  • TypeScript’s recursion limit for the path seems to be at around 10 children.

How does it work?

For a type T and a path P, the algorithm is:

  1. Split P as in P.split("."), assign it to P
  2. Let Valid be "" and CurrentT be T
    • If P is empty, return [CurrentT, Valid]
    • Else let Hd be the head of P as in P.shift()
      • If Hd is a valid key of CurrentT
        • Assign T[Hd] to CurrentT
        • Join Hd to the end of Valid, with a "." if Valid is not empty
        • Goto 3.
      • Else return [CurrentT, Valid]
  3. Let Keys be the keys of the object CurrentT
  4. Return the union of Valid and Valid × Keys

The returned union contains the original P if it is a valid path.

AutocompleteHelper is a no-op that forces the IntelliSense engine to reevaluate both of its operands. This is the black magic that provides dot notation-like autocompletion when you press . on your keyboard.

Equivalent value version

Since metaprogramming in TypeScript is a pure functional language, it is easy to rewrite it for values instead of types. The following code block may be easier to understand than the original .d.ts file.

const Digits = "0123456789";
function IsNumberImpl(S) {
    if (S === "") {
        return true;
    }
    const m = S.match(/^(.)(.*)$/);
    if (m) {
        const [, _Hd, _Tl] = m;
        if (Digits.includes(_Hd)) {
            return IsNumberImpl(_Tl);
        }
        return false;
    }
    return false;
}
const IsNumber = S => S === "" ? false : IsNumberImpl(S);

function Split(S, _Acc = []) {
    const m = S.match(/^(.*?)\.(.*)$/);
    if (m) {
        const [, _Hd, _Tl] = m;
        return Split(_Tl, [..._Acc, _Hd]);
    }
    return [..._Acc, S];
}

const SafeDot = S => S === "" ? "" : `${S}.`;

function ExistingPath(T, _Path, _Valid = "") {
    const [_Hd, ..._Tl] = _Path;
    if (_Hd !== undefined) {
        if (Array.isArray(T)) {
            if (IsNumber(_Hd) === true) {
                return ExistingPath(T[0], _Tl, `${SafeDot(_Valid)}${_Hd}`);
            }
            return [T, _Valid];
        }
        if (Object.keys(T).includes(_Hd)) {
            return ExistingPath(T[_Hd], _Tl, `${SafeDot(_Valid)}${_Hd}`);
        }
       return [T, _Valid];
    }
    return [T, _Valid];
}

function SafeKeyof(T) {
    if (Array.isArray(T)) {
        return "<index>";
    }
    if (typeof T === "object") {
        return Object.keys(T);
    }
    return "";
}

function GenerateValidPaths(T, _Path) {
    const [_CurrentT, _ValidPath] = ExistingPath(T, _Path);
    const _Keys = SafeKeyof(_CurrentT);
    if (_Keys === "") {
        return [_ValidPath];
    }
    return [
        _ValidPath,
        ...[_Keys].flat().map(k => `${SafeDot(_ValidPath)}${k}`),
    ];
}

function IntelliPath(T, _Path) {
    return GenerateValidPaths(T, Split(_Path)).filter(v => v !== "");
}

You can copy-paste this code in any modern REPL (like your browser) to get the same behaviour as the type version:

> const T = {
    a: {
        b: 12,
        c: ["a", "b"],
    },
    d: "string",
};
undefined
> IntelliPath(T, "")
[ "a", "d" ]
> IntelliPath(T, "a")
[ "a", "a.b", "a.c" ]
> IntelliPath(T, "a.c.hi")
[ "a.c", "a.c.<index>" ]

Contributing

Don’t hesitate to open an issue or a PR if you’d want more features, or if you see that something’s missing (even if it’s a typo).

There is no format for issues or commit messages. Just try to stay within the 60 character limits for commit titles, and write them in an imperative sentence.

Testing

Tests are in the t/ folder. If Perl isn’t installed on your system, you can open the .ts files there and check if there are compilation errors in your IDE.