-
-
Notifications
You must be signed in to change notification settings - Fork 204
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
[gjs-gts-parser] fix type aware linting when using ts+gts files #1996
Conversation
fe54a17
to
a70475d
Compare
5efa7b2
to
486d505
Compare
}, | ||
{ | ||
files: ['**/*.ts'], | ||
parser: '@typescript-eslint/parser', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@patricklx don't we need to leave *.ts
files parsing to eslint-plugin-ember/gjs-gts-parser
also?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't use it then I assume we will again have errors if we try to import from a .gts
file inside a regular .ts
file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though this means that we will need to recommend ppl use eslint-plugin-ember/gjs-gts-parser
for all files js/ts/gjs/gts
in order to have it work properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i decided now to just overwrite the TS.sys functions, typescript-eslint already handles those cases and with this we keep it like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aha I see, so basically we just monkey patch ts
directly. Might be a bit brittle but it will work :D.
And we don't need to worry about creating our own TS program nor involving glint :D
Solid idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem i'm facing now is how to replace .gts
without breaking some eslint rule...
especially the stylistic ones.
it would shift all code by 1 character... but adding something after it breaks some whitespace rules.
So now i use the extension .mts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though this means that we will need to recommend ppl use
eslint-plugin-ember/gjs-gts-parser
for all filesjs/ts/gjs/gts
in order to have it work properly
Maybe, now ts.sys is overwritten as soon as gjs-gts-parser is loaded. Which is loaded during config loading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@patricklx yes you are right, with ts patch approach we can leave ts
files to typescript-eslint/parser
.
Again solid idea patching this :)
7e496d9
to
db50e78
Compare
const resultErrors = results.flatMap((result) => result.messages); | ||
expect(resultErrors).toHaveLength(3); | ||
|
||
expect(resultErrors[0].message).toBe("Use 'String#startsWith' method instead."); // Actual result is "Unsafe member access [0] on an `any` value." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@patricklx We should probably remove this comment now
lib/parsers/ts-utils.js
Outdated
return content; | ||
}, | ||
}; | ||
ts.setSys(newSys); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
found this, now a bit better than monkey patching
1b23108
to
dd72c1c
Compare
3e1e945
to
7feaa03
Compare
@patricklx this seems to be working ok now as far as I can tell :D |
8608c46
to
1b5d584
Compare
@bmish I squashed the commits now. this is ready |
Will we be able to merge this soon? |
1b5d584
to
7268a31
Compare
rebased |
lib/parsers/ts-utils.js
Outdated
|
||
module.exports.patchTs = function patchTs() { | ||
const readDirectory = ts.sys.readDirectory; | ||
ts.sys.readDirectory = function (...args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears to be overriding TypeScript functions with our own version, correct? Couldn't this affect other code using TypeScript in the user's application/repository? I don't think it's safe nor acceptable to be modifying dependencies like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only inside eslint. It should work normally as we also call the original function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's how I understand these sorts of modifications, too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you find some evidence for that? I'm not convinced that's the case. TypeScript is likely installed just once in the user's repository, assuming everyone requests v5. eslint-plugin-ember doesn't necessarily have its own copy of TypeScript. So I suspect you are modifying TypeScript used by others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not to mention, this is not a supported API for adjusting TypeScript behavior, and presumably could break with any future version update...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this code is to be removed from eslint-plugin-ember to ember-eslint-parser (and then that dep added back here), and it's not the type of patch you think it is.
Happy to hop on a call if you like!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its actually not monkey patching anymore.
Its passing our own sys to typescript via ts.setSys() if that makes any difference.
https://github.com/ember-cli/eslint-plugin-ember/pull/1996/files#diff-e244765e7358fb9a23e94c9b7d23917d4ed0172bb7550fbc1ff88359691859feR12.
It's similar to how it's done on the ts.program object in typescript-eslint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll be PRing the removal of all this parser code shortly, and then be collabin with Patrick to reintroduce via single dep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like setSys is not public.
Anyways, I'll go ahead and release another alpha version shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the beginning of the extraction #2028
} | ||
} | ||
// block params do not have location information | ||
// add our own nodes so we can reference them |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we do anything with block params? the whole glimmer AST should not be needed for gjs/gts parsing (as far as linting is concerned)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reorganized the files as parser file was getting to big
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but that doesn't answer the question 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it's to have the rules no-undef and no-unused work in gjs/gts templates. Like this we have the exact location of no-undef.
And it also works for block params. Marking them unused when not used.
And js vars, that are in use in the template will not be marked as unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see it in thte tests for the parser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I guess, how much code is there just to support no-undef and no-unused having exact locations?
maybe it's no big deal -- but also maybe @glimmer/syntax
should support having offsets passed to it for all the AST nodes to reduce the Math being done in eslint-plugin-ember 🤔
maybe content-tag, or another library should be responsible for stitching together the concepts of:
- content-tag (split
<template>
from js/ts) - parse AST for JS/TS
- parse AST for the
<template>
s - stitch all the ASTs together with correct offsets for the file
- somehow have methods to allow writing back to the file for any given sub-AST range
(I see now why there is so much code involved with all of this, that is a lot of steps, and I feel like most folks overwhelmed by all this code (including me, maaaaybe @ef4 (ie: put everything in content-tag), didn't understand the full scope of the problem)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we only have this configuration for gjs/gts in template-lint: https://github.com/ember-template-lint/ember-template-lint/blob/master/lib/config/recommended.js#L98
Right, template-lint doesn't handle js scope. Here it can handle both.
It also is more code to supporting other rules that are token based
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the code could be simpler if glimmer/syntax provides ranges for block params and for the html tag parts split by .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will happily take PRs on that repo that improve the parser!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit behind on this PR, so apologies while I ask a bunch of stupid questions 😅
I was looking at eslint-plugin-svelte, and they have something like this:
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: {
// Specify a parser for each lang.
ts: '@typescript-eslint/parser',
js: 'espree',
typescript: '@typescript-eslint/parser'
}
}
}
would that work for us / simplify this code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, shouldn't parserOptions.programs
be set somewhere? https://github.com/typescript-eslint/typescript-eslint/tree/v5.30.7/packages/parser#parseroptionsprograms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we set a program, we also have to handle program updates... ts-eslint has a bunch of code fir that.
which is ehy i opted to pass a custom sys to ts.setSys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, thanks!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well wait -- what do you mean by program updates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NullVoxPopuli either way it couldn't hurt to covert the code to ESM.
If you are up for it I can try to spend some time next couple of days and convert the codebase to ESM if you guys would be willing to review.
After that we can try Glint approach more easily and see if it will work? Wdyt? cc: @patricklx, @bmish
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personally, I think since we're preparing for a major, now is a great time to convert to ESM -- (esp as that conversion is much easier to land than type-aware linting, which we still have lots to document and talk about (to spread knowledge, context, document issues, document with the TS team, etc))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NullVoxPopuli I think we actually can't convert to ESM
, because eslint
package uses cjs
so it won't be able to import parseForEslint
function as well as rules if we converted them to ESM
...
So I think unless glint packages start publishing cjs only approach we have is trying the dynamic import.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's correct. I tried to make an esm plugin for eslint once and it only works with the new configuration files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting to ESM may be a large effort best saved for the next major version (not the in-progress v12 release which is already packed with breaking changes) and after ESLint v9 is out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting another unrelated thread, with some historical artefacts:
- TS Member says "Vue is custom, and 'doing weird things'"
Single run detection for type-aware linting causing file not found in any of the provided program instances typescript-eslint/typescript-eslint#4093 (comment)
Leading to the exact issue we've been running in to: New vue file will getParsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
when typescript 3.9-4.0 in VSCode typescript-eslint/typescript-eslint#2127
This issue is still open even: Looking for champion to better support Vue in typescript-eslint vuejs/eslint-plugin-vue#1296
and vue-eslint-parser and typescript-eslint problems vuejs/vue-eslint-parser#104
So, given this, I am not surprised making TS work with gjs/gts is a fair bit of work. And I have a hunch this is why the Vue ecosystem has optional support for jsx/tsx, because TS built in jsx/tsx into TS parsing with no option for other syntax plugins
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is one such way to parse custom files: https://github.com/ota-meshi/typescript-eslint-parser-for-extra-files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could even add gjs/gts to here: https://github.com/ota-meshi/typescript-eslint-parser-for-extra-files/tree/main/src/transform
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like none of this matters tho, because svelte seems to have fixed it all,
their config:
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though, this requires generating a `.svelte-kit` folder before it can work for some reason
old comment, because they break general expectation of eslint and "just working on source files":
oh nevermind, they seem to actually be further behind: sveltejs/kit#11209
const glimmer = require('@glimmer/syntax'); | ||
const DocumentLines = require('../utils/document'); | ||
const { visitorKeys: glimmerVisitorKeys } = require('@glimmer/syntax'); | ||
const TypescriptScope = require('@typescript-eslint/scope-manager'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6f21d2f
to
35c39d3
Compare
extraFileExtensions: ['.gts'], | ||
}, | ||
extends: [ | ||
'plugin:@typescript-eslint/recommended-requiring-type-checking', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this config? I didn't see it listed here: https://typescript-eslint.io/linting/configs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be an old config name but it's for type-aware linting: https://typescript-eslint.io/linting/typed-linting/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'plugin:@typescript-eslint/recommended-type-checked',
vs
'plugin:@typescript-eslint/recommended-requiring-type-checking',
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type-checking is deprecated, but an alias for type-checked
|
||
expect(resultErrors[2].line).toBe(8); | ||
expect(resultErrors[2].message).toBe("Use 'String#startsWith' method instead."); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we also add a test for a failure case for a type-aware lint?
Like, any of
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these changes are looking really good, and I super appreciate the work you've all been doing on figuring this out.
(It seems that @patricklx and @vstefanovic97 are some of the first folks to figure out type-aware linting for non-js/x syntaxes!!!!)
I'm approving to move things forward, though I'd like to see a type-aware error test added before merge.
After merging, How do ya'll feel about extracting the parser to a separate package (so it can have its own tests, lots more tests, etc, and be conceptually separated from "lint configuration", which is what the spirit of eslint-plugin-ember is?)
Then, eslint-plugin-ember can bundle that extracted package as a dependencies
entry, so consumers don't need to worry about installing anything separate.
I have a feeling this would put many of the lint-package maintainers at ease, as parsing and line-offset-altering is intense, and not for the faint of heart!
(It also allows us to iterate on the parser without re-releasing eslint-plugin-ember (thanks semver!))
edit: Made the repo here, and added @patricklx and @vstefanovic97 as collaborators on it
https://github.com/NullVoxPopuli/ember-eslint-parser
7393d21
to
77fe5c8
Compare
@NullVoxPopuli found a way to sync the mts & gts internal source files. |
77fe5c8
to
97d7398
Compare
set our own ts.sys with ts.setSys typescript-eslint has handling for a lot of scenarios for file changes and project changes etc use mts extension to keep same offsets we also sync the mts with the gts files
97d7398
to
a93ea4b
Compare
@NullVoxPopuli there are already type aware tests on the string with the [0] check. Lint needs to know that it is actually a string. Otherwise it will not show that error |
this passes our own ts.sys to typescript, which emulates gts files as mts files.
We use mts extension, so that we keep the same length of import statements and so not move offsets when replacing the extension in imports.
fixes #1995
also cleaned up the code as the file was getting too big.