-
-
Notifications
You must be signed in to change notification settings - Fork 2
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
VFileMessage generates invalid position if no position is given #15
Comments
I think that’s just Particularly I think your second link, https://github.com/unifiedjs/unified-language-server/blob/main/lib/index.js#L70-L74, is always a good idea. We’re dealing with users here, and should expect some garbage. Not everyone uses TS. And even if TS is used,
Line 185 in e5e87f2
|
The unist types match the specification for I won’t argue defensive programming is a good idea. People can indeed pass in incorrect data. But I do believe that official unified utilities shouldn’t create incorrect data. |
The code is there to make things easier before |
Friendly ping! :) |
I think we’re on the same line that currently At this point,
But this comes at the cost that checking Also now that the oldest supported Node.js version supports optional chaining, perhaps this is a good time to change it. I think it would also be nice timing to combine this with a major unified release (which is necessary because of unifiedjs/unified#202) |
The main problem I believe is that this project accepts a single point and exposes that information. The utilities typically support that starting point too as far as I am aware. |
Is there any particular reason |
Interesting idea. Positions are supposed to be ranges. E.g., the
In micromark/markdown-rs, events are never empty. This is carried through to mdast as well, and I believe every mdast/hast/nlcst/xast utility follows it too |
This reminds me of the way Monaco editor handles TypeScript diagnostics. TypeScript text spans use the format I think this applies the exact same way in unist, or any text format. A text range can have a length of 0 characters, but the best way to display a range of length 0 is often to point to the character at that index. |
But then you’re suggesting to not support points at all, that users should pass a position of (pseudocode) |
No, I suggest that a user can use: vfile.message('hello', { line: 1, column: 1 })
assert.equal(vfile.position, { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } })
assert.ok(vfile.position.start !== vfile.position.end) But tools such as import { stringifyPosition } from 'unist-util-stringify-position'
assert.equal(stringifyPosition({ start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }), '1:1')
assert.equal(stringifyPosition({ start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }), '1:1-1:2') Although it might look less nice, I don’t really see any practical problems with the following representation: assert.equal(stringifyPosition({ start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }), '1:1-1:1') |
I still feel a bit weird about positions having the same start and end points. It feels like that shouldn’t occur. It feels “wrong”.
That’s a bit in line with what I feel as “wrong”. Same start and end is really empty. No line rendered. Maybe I’m on my own here though, or have to get used to it? |
For example Also cursors in a text editor represent a point, yet most terminal emulators display a cursor as a range of length 1. |
Yeah, good “points”. I agree that that’s good on the “accepting” side of the API. But I feel weird about exposing it from an API. In this case, |
I’m leaning towards making it: position?: {start: Point; end?: Point | undefined} What do you think? |
I feel pretty strongly about using a real full unist
I understand this feeling, but I don’t think this is a strong argument to stick with optional values. I’ve worked with LSP and Monaco editor, which deal with similar situations. They do use matching Another big downside of making export function doSomethingWithPosition(position: Position): unknown It would be really nice if we could do this: for (const message = file.messages) {
doSomethingWithPosition(position)
} The only libraries I can imagine are slightly negatively affected are those that represent the position as a string (only @ChristianMurphy I would love to hear your opinion on this as well. :) |
We could tighten that down with better typings.
we could validate the data as it is passed in, throwing exceptions invalid data on the JS side.
I would read a defined
That makes sense, and lines up with what I've seen in other text editors as well.
An even then, those libraries would just need to be aware that start and end could be a point, correct? |
I think there is a large runtime cost associated with all of this. But also a cost of how to type APIs that accept numbers currently. It’s a massive change in 500 repos. I don’t understand the benefits yet.
Then it isn’t really about this project, but lots of projects, and it should be described in
It is indeed not assignable, this issue can be solved without it being assignable. We can use types than make end optional, and then you can use an if statement: if (position.end) {
// Now it’s a full position, a range.
} else {
// There’s just one point.
} There are several ideas floating around in this discussion.
|
Options for the implementation changes:
I really don’t see any downsides to option 3. Both Christian and I have seen this works in practice in other places that use positional information. |
Look at the docs on
It explicitly states that “empty” is impossible. |
You’re right about it being documented. I needed to read more thoroughly. I still don’t think this would cause any practical issues though. Personally I would prefer to update the spec to explicitly allow an empty Considering upcoming breaking changes in the ecosystem: I think it would be an improvement if a nullish {
start: { line: 1, column: 2 },
end: { line: 1, column: 2 }
} {
start: { line: 1, column: 2 },
end: null
} {
start: { line: 1, column: 2 },
} null |
It still feels very weird to me to change unist in a way to support something that never actually occurs in unist. unist is supposed to represent ASTs in different programming languages. Why should it explain things that can never happen in an AST? Why should someone implementing unist in Swift or whatever have to support something that won’t ever happen? Just because this one utility in JavaScript accepts other structures than Your code in this last comment seems to follow solution 2 in your earlier comment ( I personally prefer exposing |
There’s one place where start and end are the same: a root node representing an empty document (https://github.com/syntax-tree/mdast-util-from-markdown/blob/e30d9c8025d726360123ae95beb9cf011335b921/dev/lib/index.js#L380). I’d think it’s better to explain this in the unist document though as an exception to the rule, rather than that all nodes can have same start/end points. |
I actually think this is a good case that shows empty positions do exist, although they are rare. Typically a position is part of an AST node from a parsed text document. In text documents, syntax is mostly defined by a non-zero length range of characters. Although empty ranges could have meaning. Hypothetically a JavaScript string {
type: 'string',
position: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
children: [
{ type: 'stringQuote', value: "'", position: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } } },
{ type: 'stringContent', value: 'example', position: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } } },
{ type: 'stringQuote', valye: "'", position: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } } }
]
} With such an example, an empty string ( {
type: 'string',
position: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
children: [
{ type: 'stringQuote', value: "'", position: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } } },
{ type: 'stringContent', value: 'example', position: { start: { line: 1, column: 3 }, end: { line: 1, column: 3 } } },
{ type: 'stringQuote', valye: "'", position: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } } }
]
} I also think there’s a difference between unist nodes and vfile message positions. Positions on unist nodes typically refer to parsed content, which is typically non-empty. Vfile message positions are constructed from a unist node / position / point. Also I looked into the terminology of point / position a bit, as I find it confusing that |
The reason the field is called
This could be done like that, but it’s currently explicitly not done anywhere, and it’s explicitly documented that it doesn’t happen. OK, some examples to think about: Empty documentSay there was a lint rule like https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/no-empty-file.js. In a project How would that work? How could VS code or whatever highlight (“mark red or so”) that problem? No tabshttps://github.com/remarkjs/remark-lint/blob/main/packages/remark-lint-no-tabs/index.js. For Final eolhttps://github.com/remarkjs/remark-lint/blob/main/packages/remark-lint-final-newline/index.js. It currently doesn’t pass a point but it probably should? For |
This commit replaces a potentially incorrect `position` (`Position`-like value) field with a correct `place` (`Position | Point`) field. Then it adds support for a single options object as a parameter, which can contain the current parameters as fields, but also adds support for a `cause` error and an `ancestors` node stack. Closes GH-15. Closes GH-16. Closes GH-17. Closes GH-19. Reviewed-by: Remco Haszing <remcohaszing@gmail.com>
Initial checklist
Affected packages and versions
3.1.2
Link to runnable example
No response
Steps to reproduce
Expected behavior
The message position is undefined.
According to the type definitions it’s optional, but currently it’s always set.
Actual behavior
The message position is a unist position, but the start and end line and column are set to
null
.According to the unist types, start and end line and column can’t be null.
This causes various type errors. For example:
This test explicitly asserts the incorrect behaviour:
vfile-message/test.js
Lines 63 to 66 in e5e87f2
Affected runtime and version
node@18.8.0
Affected package manager and version
npm@8.17.0
Affected OS and version
Pop!_OS 22.04
Build and bundle tools
No response
The text was updated successfully, but these errors were encountered: