-
-
Notifications
You must be signed in to change notification settings - Fork 15
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
Added codeAction (extract subSchema to defs) #133
base: main
Are you sure you want to change the base?
Conversation
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 is great progress! But, it looks like there's still some work to do.
Adding the definitions to the top isn't good enough. Also, the sloppy formatting of the generated code isn't acceptable either. The biggest motivating factor for this project is to encourage best practices and good style. Two of those rules are that $schema
and $id
always come first and that $defs
always goes last. It's important that the definitions are in the right place and reasonably formatted when moved. You might have to somehow run the moved code through a formatter once it's in its new location. jsonc-parser is used internally to parse the schema. It has a formatting feature that you might be able to use somehow.
I noticed that the schema you're using in your demo video has a problem. The "shipping_address" property has a property "address" that then has the actual schema in it. That subschema will do nothing at all. "address" is not JSON Schema keyword, so it gets ignored. You can tell something's wrong because the schema in "address" doesn't have the expected syntax highlighting. JSON Schema keywords should be highlighted differently than plain JSON properties.
I see you're using the property name as the definition name. That's a nice default, but that's not always going to work. Subschemas can be in quite a few places other than properties
values. For example, this would work for the items
keyword. I think what needs to happen is that the user needs to provide the definition name. Have a look at the if
/then
completion provider. There's a special syntax that allows you to set placeholders that the user will be prompted to fill in. That approach may also work here.
You'll need to add tests for this feature that cover whatever edge cases you can think of.
I appreciate the clean and well written code so far. I suggest installing the ESLint plugin in your editor to get linting feedback while developing. It will help avoid getting build errors in your PRs if you forget to run the linter before pushing.
Thank you for the detailed feedback. However, I encountered an issue while looking into what you mentioned:
I found that |
Thanks for figuring that out. Yes, it looks like SnippetTextEdit is what we need. Although 3.18 isn't released yet, it looks like |
I think it's not supported by As you can see in the screenshots, the latest available version is still 3.17.5, and this feature is introduced in 3.18. I also checked the nodeModule Screenshot
![]() |
Yes, you're right. Although the code was merged over a year ago, it hasn't made it to an official release yet. It's planned for the next major release (v10) and that only has pre-releases published so far. We could change the dependency to `"vscode-languageserver": "^10.0.0-next" to use the pre-release. But, that's only the server. It probably wouldn't work in vscode yet. I guess that means we have to hold off on that detail. I was hoping this would give us a way to avoid having to come up with a more robust way to generate definition names, but it looks like we're going to have to find a temporary solution until this feature is released. |
I think, for now, we should keep it simple and implement a basic defCounter that generates definition names like def1, def2, and so on. Do you have any other solutions in mind? Please let me know your thoughts. |
Number based naming can work, but there are some some edge cases. Extract a schema and you'd get Also, if you do refactorings in one schema and then go to another schema, it would be weird for it to generate One thing that I think would work is the generate number-based names starting at Or, you could inspect all the definition names looking for the Or, you could generate a UUID and use that as the name and not have to check anything. But, a bulky UUID might not make for as good a user experience. Any of those options would be ok. |
Alright, I’ll work on it and let you know once it’s done. |
Screen.Recording.2025-02-24.at.12.48.49.AM.movPlease review it. If everything looks good, I will start writing the test cases. |
When switching the dialect URI of the schema, we need to change |
Use the |
Got it! I'll use |
I just made the change, you can check it now. I'll write the test case as soon as possible. |
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.
Have another try at the JSON formatting part. The other things I mentioned should be small and easy to address. I included a few code style suggestions that aren't caught by the linter. In general, I thought the code made better use of whitespace the last time I reviewed. Things are more compact now making the code harder to read.
// Helper function to format new def using jsonc-parser | ||
const formatNewDef = (/** @type {string} */ newDefText) => { | ||
try { | ||
/** @type {unknown} */ | ||
const parsedDef = jsoncParser.parse(newDefText); | ||
return JSON.stringify(parsedDef, null, 2).replace(/\n/g, "\n "); | ||
} catch { | ||
return newDefText; | ||
} | ||
}; |
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.
Pull this out of the constructor. Either make it a private function or a utility function outside of the class. I'm sure other refactorings will need to use a function like this as well, so maybe it belongs in util.js?
const parsedDef = jsoncParser.parse(newDefText); | ||
return JSON.stringify(parsedDef, null, 2).replace(/\n/g, "\n "); |
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 isn't what I meant when I suggested jsonc-parser. You're not using it for anything you couldn't have used JSON.parse
for. Look for the format
function from jsonc-parser
. This solution using JSON.stringify
to format isn't going to work for embedded schemas.
{
"$defs": {
"this-is-an-embedded-schema-because-it-has-$id": {
"$id": "my-embedded-schema",
"$defs": {
"def1": {
"$comment": "This definition will need more indentation because it's nested"
}
}
}
}
}
jsoncParser.format
should help get around that problem because you can give it the whole document with the replaced text and tell it to format just the replaced text.
try { | ||
/** @type {unknown} */ | ||
const parsedDef = jsoncParser.parse(newDefText); | ||
return JSON.stringify(parsedDef, null, 2).replace(/\n/g, "\n "); |
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.
Hardcoding the indentation strategy to two spaces isn't going to work. You're going to need to determine what indentation strategy the client is using and match that. You should be able to get the information from the configuration service, but it's currently only configured to retrieve this server's configs. I think there's another "section" you'd have to request, but you'll have to figure out what that is.
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 have made all the changes, but I am stuck on the formatting part. It always takes the default tab size of four instead of the current tab size. Even though I am fetching the editor settings, it doesn't seem to reflect the actual tab size used in the document. Do you have any suggestions on how to correctly retrieve the active document's indentation settings?
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.
Try sending the document's URI (schemaDocument.textDocument.uri
) in the scopeUri
parameter in the configuration request. That should return the settings for that file, instead of the settings for the workspace. That's my best guess.
I think it should be ok to add an optional parameter to the get
function so you can pass in the document URI.
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 also tried this approach, but when I console it in the terminal, it still always shows 4. Not sure why it's not picking up the actual tab size. Any other suggestions?
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 don't have any more guesses. I'll try to find some time tonight to try some things and see if I can figure anything 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.
Here's what I figured out.
I think getting 4 is technically correct. Your editor is configured for a 4 space indentation by default. However, vscode also has a setting called "Detect Indentation". If that is set, it ignores the tabSize
setting and figures out the indentation based on the content of the file. I haven't found any way to get vscode to tell us the detected indentation. There might not be a way. I think that all we can do is check for the detectIndentation
config and if it's true, do our own indentation detection on the server.
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.
Okay, then I'll set detectIndentation: true and handle the indentation detection. Thanks for your help!
const defsContent = schemaDocument.textDocument.getText().slice( | ||
definitionsNode.offset, | ||
definitionsNode.offset + definitionsNode.textLength | ||
); | ||
const defMatches = [...defsContent.matchAll(/"def(\d+)":/g)]; | ||
defMatches.forEach((match) => | ||
highestDefNumber = Math.max(highestDefNumber, parseInt(match[1], 10)) | ||
); |
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 isn't a good approach. Use keys
from schema-node.js
to loop over all the property names of the definitionsNode
. Then you can use the regex on those values to determine the highestDefNumber
.
Co-authored-by: Jason Desrosiers <[email protected]>
Co-authored-by: Jason Desrosiers <[email protected]>
Co-authored-by: Jason Desrosiers <[email protected]>
Co-authored-by: Jason Desrosiers <[email protected]>
Co-authored-by: Jason Desrosiers <[email protected]>
#132
Point to Note in this:-
Screen.Recording.2025-02-20.at.10.47.01.PM.mov