New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@typescript-eslint/types has a phantom dependency on the "typescript" package #3622
Comments
Note that the dependency is specifically a type-only import - it is not a runtime dependency. typescript-eslint/packages/ast-spec/src/token/PunctuatorToken/PunctuatorTokenToText.ts Line 1 in 407a374
We purposely do not declare a peer dependencies on typescript in any of our packages because there are many packages that depend on our tooling in non-typescript contexts. If we declared a peer dep then yarn/npm/etc would throw warnings at users for no real reason. We declare the dependency as an optional peer dependency, but it looks like adding it as an optional peer dependency got missed (this comment got missed). We can definitely add the optional peer dependency config like we have for our other packages though! We could probably hack around our reliance on the latest version using a similar trick to this:
|
This would be the right criteria if the .d.ts file was not part of the exported public API. But it is.
Both sound good to me. I can make a PR, but let me know if you prefer a particular approach. |
definitely the optional peer dep so we're explicit (I know yarn2 can break sometimes without this): typescript-eslint/packages/typescript-estree/package.json Lines 69 to 73 in b15a2b2
As for some form of "manually define the new enum members" hack so we can ensure we're backwards compatible.... it looks like we can't do it :( import { SyntaxKind } from 'typescript';
declare module 'typescript' {
export enum SyntaxKind {
BarBarEqualsToken = -1, // Duplicate identifier 'BarBarEqualsToken'.
AmpersandAmpersandEqualsToken = -2, // Duplicate identifier 'AmpersandAmpersandEqualsToken'.
QuestionQuestionEqualsToken = -3, // Duplicate identifier 'QuestionQuestionEqualsToken'.
}
} Hmm....
The interface exists so that we can ensure we're declaring all of the token strings based on what TS declares (this set of enum members here: https://github.com/microsoft/TypeScript/blob/8e795108eb0d35b36048ee1b0e55ca154a72a5ea/src/compiler/types.ts#L481-L540) So externally the interface's keys aren't seen, just the interface's values (which are not based on TS) But API extractor can't automatically eliminate this complex relationship for us. I wonder if there's a way to hack this in to the extractor step? |
I feel like there is a simpler solution. Let me take a crack at it over the weekend. |
Draft PR #3623 illustrates how the test could be rewritten so that the mapping table does not become part of the public API. It requires maintaining two lists ( With this change, Let me know if you like this solution. |
BTW I was curious about how this project gets built: api-extractor.json reads |
It's not a |
The whole point of doing it like I did, was to not have to duplicate values and keeping them in sync. Although #3529 created the tests, so it's now checked that every value in the TS
This makes your PR is removing 3 values that are currently present though 🤔 |
Besides eliminating the
I was curious about that. The current code references definitions that don't exist in the Since |
These 3 values were already in there before I did the refactor, so that's why I kept them
They will be included in v4.4 (hence the comments next to these values) |
@MichaelDeBoey I see -- v4.4 hasn't been released yet.
Let me know which way you want to go. And if there are no objections to my approach otherwise, I can finish the draft PR and get this wrapped up. |
It's currently out there, so I think we should keep supporting them and include them temporarily.
I think we should only support released features, so having a temporary solution would fit for now I think.
We can do this after v4.4 is released and set as the dependency in this package I think. As I'm not a maintainer on this project, let's see what @bradzacher thinks. |
I'm still waiting to hear from @bradzacher |
Maybe @JamesHenry can also help out here? |
Sorry to delay. I've been taking some personal time off of open source for the last few weeks (trying to avoid anything non-trivial). I'll get back to this next week. I've got quite a backlog to traverse |
I am upvoting and watching this issue because it currently breaks compatibility to TypeScript 3.9.9 for me. According to the readme, |
Oookay. First up - in regards to the build process.. Yeah it is pretty hacky. In a nutshell: When I split the AST spec into its own "package", I had two goals:
So my solution was to use a type bundler to flatten the spec into a single file and then copy it across to I had to hack it a bit because every bundler made the enums a declaration.. so I had to make them non-declared when I copied it across to the source directory.
I know you've worked on this stuff before @octogonz, so if you've suggestions for a better way to do it I'd be happy to take suggestions (or a PR 😄 ).
My take is that we should support whatever the types exported by TS supports to keep maintenance as simple as possible. I'd rather have a few "unreleased" members and strict consistency than exclude them and have the possibility of us forgetting to add new members later. I do agree that I like not having to duplicate things. Thinking out loud... I wonder if there is a way we can just accomplish this via a build step? Probably way more work than we should put in though TBH. @octogonz might know... can we get around this by using the Seems like it might just be another hacky hack in the system. Might be good to just "do it properly".. Ultimately I'm okay with duplicating it. It's probably the easiest way for us to remove the unnecessary dependency. |
Interesting - I tried quickly adding
|
It would, but because the TypeScript API is complex and not factored nicely for API Extractor, it is likely to pull in unrelated definitions. Before we start setting up a repro and debugging API Extractor, I'd like to understand what is wrong with the draft PR #3623. What problem are we trying to solve? As I understand it: Pros for PR #3623 approach:
Cons:
A complex inlining transform could eliminate this manual work, but potentially with the cost of making the overall system harder for a casual observer to understand. That might not even be a compelling tradeoff. I guess I'm just questioning why we should invest more time in this problem if we already have an okay solution. |
I was just wondering if we could hackily solve this with a one-line change. But the bug prevents it. 🤷 For sure - I'm okay with the duplicating route as long as the tests ensure we don't accidentally desync. |
As long as we can keep all values that are currently present, I'm OK with duplicating (until we find a better solution) too 👍 |
Ok, lemme find some time, and I will fix up my draft PR so we can get this fixed. |
@octogonz Is there something I can help out with to get your PR ready? |
IIRC the build was failing, maybe because of a unit test. If someone can fix that, it would be helpful. I've been on vacation so haven't had time to look at it yet. |
@octogonz I've been taking a look at this and it seems the errors are coming from the fact that typescript-eslint/packages/typescript-estree/src/node-utils.ts Lines 16 to 24 in 043462e
I could expose your |
Thanks, that's helpful! It seems that when I made my draft PR, I didn't try building the entire monorepo, so I wasn't aware that typescript-eslint/packages/typescript-estree/src/node-utils.ts Lines 56 to 62 in 62bcc93
This wrapper maps the For example: typescript-eslint/packages/typescript-estree/src/convert.ts Lines 2375 to 2381 in 62bcc93
In the above code Thinking about the big picture:
Some options:
The last one seems like a good avenue to explore. |
So if we can get these types exposed by TypeScript, we could remove the whole
Just using That's actually where the whole idea of extracting
Wouldn't that cause the same problem as we have right now? |
That's a solution I hadn't thought of. However these requirements /might/ be somewhat specific to
Lemme follow up on this question. (IIRC the whole problem I had is that |
Yes, when my project imports import type { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; ...it imports from So This whole problem seems to boil down to a conflict that:
We can preserve I'm out of time for today, but I'll see if I can come up with another iteration along these lines. Or if someone else has ideas, that would help push this along. |
@octogonz Creating a new |
@bradzacher What do you think is the best way forward here? |
If we had a dedicated set of maintainers that were onboarded onto tribal knowledge and able to properly maintain and review everything - I'd be all for that approach.
This is the crux of the issue - we have these interfaces that are really internal to the codebase, but are exposed via the types. Eg we have export interface PunctuatorToken extends BaseToken {
type: AST_TOKEN_TYPES.Punctuator;
value: ValueOf<PunctuatorTokenToText>;
} However This is a little bit difficult to solve because right now:
In terms of solutions to these problems:
In terms of picking a solution: (3) would be the "simplest" solution from the POV of our codebase as it would be a one-line change and everything would "just work", but it's probably a huge pain in the ass to find and fix this bug in api-extractor (if it's even possible). (4) would only half solve the problem because I think the best path forward might be something like (1.iv) + (2.ii). To clarify, what I'd suggest is this:
What do you guys think? |
@octogonz Any news on the new implementation? |
This sounds good, although I'm a bit fuzzy on the specific details to implement it. (I've forgotten some of the context and haven't had time to revisit this properly.) My main feedback is that this problem has been unfixed for a while... so if there's a quick+dirty hack to unblock people, maybe we could do that first, and then follow up with the better long-term solution.
@bradzacher Not sure it's relevant, but FYI you could also use API Extractor to trim
Unfortunately I've become busy with other things lately, and haven't had time to work on this. 🙁 If I do get some time, I'll post an update on this thread. |
@octogonz I was wondering if you already had some time to look further into this? |
There are more dependencies on The package is using only its types, however the CJS code that is generated is doing a Object.defineProperty(exports, "__esModule", { value: true });
exports.forEachReturnStatement = exports.getNameLocationInGlobalDirectiveComment = void 0;
const escapeRegExp_1 = require("./escapeRegExp");
const ts = __importStar(require("typescript")); // <------------------here
// deeply re-export, for convenience
__exportStar(require("@typescript-eslint/utils/dist/ast-utils"), exports); and this breaks various tools because they're not pulling in that dependency. |
eslint-plugin has an explicitly listed optional peer dependency: typescript-eslint/packages/eslint-plugin/package.json Lines 73 to 77 in 7735475
Sometimes people install these packages as transitive dependencies. If we list an explicit peer dependency we would list The optional dependency config above is is supported by yarn2, npm and pnpm. It causes them to understand that our package depends on TS. Why a peer dep and not a proper dep? Because we want to use the version of TS installed locally, and never our own.
This is false. That very file uses the TS export at the bottom of the file: typescript-eslint/packages/eslint-plugin/src/util/astUtils.ts Lines 57 to 73 in 7735475
|
@bradzacher Thank you. Yes you are right, it was my mistake, I didn't read very carefully and i confused those with being types. It's just odd that a package requires a dependency that is not listed in |
From my last comment
If someone doesn't install TS and uses our rules it'll crash. That's on them. |
Ok it's super weird this behavior, but i understand now. Thanks for clarifying twice, sorry about my confusion :) |
Repro
@typescript-eslint/parser
, with an indirect dependency on@typescript-eslint/types
@typescript-eslint/types
fails to compile if your project is not using the very latest release of TypeScript. The error looks like:Expected: We can solve this problem by upgrading the TypeScript dependency for our ESLint plugin project, while other monorepo projects continue to use the older TypeScript compiler that they target.
Actual: Upgrading TypeScript for the the plugin package does NOT upgrade the TypeScript import for
@typescript-eslint/types
. As an indirect dependency, instead it accidentally picks up the older TypeScript version that was installed for a different project. This is because the package manager is not required to make any guarantees about the version (or even existence) oftypescript
for@typescript-eslint/types
, because it was not declared in package.json.Specifically, the
typescript
package is referenced here:dist/ast-spec.d.ts
...but is only declared as a dev dependency here:
@typescript-eslint/types/package.json
This makes
typescript
into a phantom dependency, which is an incorrect practice when using a package manager with a modern node_modules layout.Suggested fix
typescript
to thepeerDependencies
section for@typescript-eslint/types
(maybe as an optional peer dependency if we are lazy), ORast-spec.d.ts
so that it does not rely on imports fromtypescript
It's possible to work around this problem using hacks, but those hacks would need to be applied to each project in this situation that somehow indirectly depends on
@typescript-eslint/types
.Versions
@typescript-eslint/types
4.28.2
TypeScript
3.9.x
and4.3.x
ESLint
7.30.0
node
12.20.1
The text was updated successfully, but these errors were encountered: