microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.93k stars 12.47k forks source link

random emit declaration #40092

Open bluelovers opened 4 years ago

bluelovers commented 4 years ago

TypeScript Version: 3.7.x-dev.201xxxxx

Search Terms:

Code

https://github.com/bluelovers/ws-yarn-workspaces/blob/master/packages/%40yarn-tool/npm-package-arg-util/lib/parseArgvPkgName.ts

import { npa, getSemverFromNpaResult} from '../index';
import { stripScope } from './stripScope';
import { IParsePackageName, IResult } from './types';

/**
 * @deprecated
 */
export function parseArgvPkgName(input: string)
{
    const result = npa(input)

    if (result)
    {
        return {
            input,
            namespace: result.scope,
            name: stripScope(result.name),
            version: getSemverFromNpaResult(result),
            result,
        }
    }
}

export function parsePackageName(packageName: string): IParsePackageName
{
    const result = npa(packageName)

    const subname = stripScope(result.name);

    let semver = getSemverFromNpaResult(result);

    if (!semver?.length)
    {
        semver = void 0;
    }

    return {
        type: result.type,
        name: result.name,
        scope: result.scope,
        subname,
        semver,
        result,
    }
}

Expected behavior:

emit .d.ts always same

Actual behavior:

random emit one of code

/// <reference types="npm-package-arg" />
import { IParsePackageName } from './types';
/**
 * @deprecated
 */
export declare function parseArgvPkgName(input: string): {
    input: string;
    namespace: string;
    name: string;
    version: string;
    result: import("npm-package-arg").FileResult | import("npm-package-arg").HostedGitResult | import("npm-package-arg").URLResult | import("npm-package-arg").AliasResult | import("npm-package-arg").RegistryResult;
};
export declare function parsePackageName(packageName: string): IParsePackageName;

or

import { IParsePackageName, IResult } from './types';
/**
 * @deprecated
 */
export declare function parseArgvPkgName(input: string): {
    input: string;
    namespace: string;
    name: string;
    version: string;
    result: IResult;
};
export declare function parsePackageName(packageName: string): IParsePackageName;

Playground Link:

Related Issues:

weswigham commented 4 years ago

OK, yeah, initial/clean compilation produces the

/// <reference types="npm-package-arg" />
import { IParsePackageName } from './types';

variant, while watch-based recompilation produces the

import { IParsePackageName, IResult } from './types';

variant (so it's at least actually predictableish).

This happens because of the file check order causing the FileResult | HostedGitResult | URLResult | AliasResult | RegistryResult union to either be made with or without an alias. When in full program compilation, index.d.ts of npm-package-arg is read first, where the union is manufactured without an alias symbol. When in watch mode, types.ts (or the declaration file thereof) of npm-package-arg-util is read first, which gives the union an alias symbol.

weswigham commented 4 years ago

We spoke about it for a bit, and because of the effect this has on watch mode (and the instability therein), we're willing to revist what I think is the canonical fix for this (and issues like it): Recording all aliases established for a type, rather than just optimistically recording the "first" (if it's established at the same time the type is manufactured). So I'll retriage this as Bug, and that'll be the gist of the associated fix, once it's ready.