Skip to content

Added codeAction (extract subSchema to defs) #133

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

Merged
merged 24 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7e39bbc
added codeAction (extract subSchema to defs)
arpitkuriyal Feb 20, 2025
8843449
changed the params to Destructured Parameter
arpitkuriyal Feb 20, 2025
ae983b8
correction in the extractSubschema.js
arpitkuriyal Feb 23, 2025
9a1f2b9
added getKeywordName to get definition keyword
arpitkuriyal Feb 24, 2025
a432651
make a function to detect the indentation of the currect file
arpitkuriyal Feb 26, 2025
ef01589
Uninstall a dependency that was installed by mistake
arpitkuriyal Feb 26, 2025
3d90464
added withformatting function
arpitkuriyal Mar 2, 2025
18abef0
corrected lint error
arpitkuriyal Mar 2, 2025
56cd7f2
Improve the withFormatting function and config file
arpitkuriyal Mar 4, 2025
5272af7
Cleanup code action text formatting
jdesrosiers Mar 5, 2025
71b147b
Eliminate duplication building configs in both onDidChangeConfig and get
jdesrosiers Mar 5, 2025
08ad10e
update .gitignore file
arpitkuriyal Mar 8, 2025
fafbfdb
do some clean up
arpitkuriyal Mar 8, 2025
0ba85fd
eliminate the duplication in config as it was added by mistake during…
arpitkuriyal Mar 8, 2025
00e1094
Some trivial whitespace cleanup
jdesrosiers Mar 8, 2025
77db1ab
fix type issues
arpitkuriyal Mar 9, 2025
9f48536
Configuration cleanup
jdesrosiers Mar 8, 2025
575aeff
Update TestClient to support multiple config scopes
jdesrosiers Mar 8, 2025
a675fc6
definition to get added to the bottom of
arpitkuriyal Mar 14, 2025
cb6110a
Add isEmbeddedSchema flag to SchemaNode to fix findNodeAtOffset
jdesrosiers May 15, 2025
12a81a8
fixed formatting issue for embedded schema
arpitkuriyal May 16, 2025
b0fb8c9
added basic test cases for codeAction feature
arpitkuriyal May 17, 2025
cf43733
added test cases
arpitkuriyal May 21, 2025
050b459
added test for configuration settings
arpitkuriyal May 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ dist/
.vscode/
scratch/
TODO
.DS_Store

10 changes: 10 additions & 0 deletions language-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"keywords": [],
"dependencies": {
"chokidar": "^4.0.1",
"detect-indent": "^7.0.1",
"ignore": "^7.0.1",
"jsonc-parser": "^3.3.1",
"merge-anything": "^6.0.2",
Expand Down
2 changes: 2 additions & 0 deletions language-server/src/build-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ValidationErrorsDiagnosticsProvider } from "./features/diagnostics/vali
import { IfThenCompletionProvider } from "./features/completion/if-then-completion.js";
import { KeywordCompletionProvider } from "./features/completion/keyword-completion.js";
import { SchemaCompletionProvider } from "./features/completion/schema-completion.js";
import { ExtractSubSchemaToDefs } from "./features/codeAction/extractSubschema.js";

// Hyperjump
import { removeMediaTypePlugin } from "@hyperjump/browser";
Expand Down Expand Up @@ -49,6 +50,7 @@ export const buildServer = (connection) => {
new GotoDefinitionFeature(server, schemas);
new FindReferencesFeature(server, schemas);
new HoverFeature(server, schemas);
new ExtractSubSchemaToDefs(server, schemas, configuration);

// TODO: It's awkward that diagnostics needs a variable
const diagnostics = new DiagnosticsFeature(server, [
Expand Down
111 changes: 111 additions & 0 deletions language-server/src/features/codeAction/extractSubschema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
CodeActionKind,
TextDocumentEdit
} from "vscode-languageserver";
import { getKeywordName } from "@hyperjump/json-schema/experimental";
import * as SchemaDocument from "../../model/schema-document.js";
import * as SchemaNode from "../../model/schema-node.js";
import { withFormatting } from "../../util/util.js";

/**
* @import { Server } from "../../services/server.js";
* @import { Schemas } from "../../services/schemas.js";
* @import { CodeAction } from "vscode-languageserver";
* @import { Configuration } from "../../services/configuration.js";
*/


export class ExtractSubSchemaToDefs {
/**
* @param {Server} server
* @param {Schemas} schemas
* @param {Configuration} configuration
*/
constructor(server, schemas, configuration) {
this.server = server;
this.schemas = schemas;
this.configuration = configuration;

server.onInitialize(() => ({
capabilities: {
codeActionProvider: true
}
}));

server.onCodeAction(async ({ textDocument, range }) => {
if (range.start.line === range.end.line && range.start.character === range.end.character) {
return [];
}

const uri = textDocument.uri;
let schemaDocument = await schemas.getOpen(uri);
if (!schemaDocument) {
return [];
}

const offset = schemaDocument.textDocument.offsetAt(range.start);
const node = SchemaDocument.findNodeAtOffset(schemaDocument, offset);
if (!node?.isSchema) {
return [];
}

const dialectUri = /** @type {string} */ (node.root.dialectUri);
const definitionsKeyword = getKeywordName(dialectUri, "https://json-schema.org/keyword/definitions");
const definitionsNode = SchemaNode.step(definitionsKeyword, node.root);
let highestDefNumber = 0;
if (definitionsNode) {
let defNodeKeys = SchemaNode.keys(definitionsNode);
for (const key of defNodeKeys) {
const keyValue = /** @type {string} */ (SchemaNode.value(key));

const match = /^def(\d+)$/.exec(keyValue);
if (match) {
highestDefNumber = Math.max(parseInt(match[1], 10), highestDefNumber);
}
}
}

const newDefName = `def${highestDefNumber + 1}`;
const extractedDef = schemaDocument.textDocument.getText(range);
const settings = await this.configuration.get();
const lastSubschema = node.root.children.slice(-1)[0];
const lastSubschemaPosition = lastSubschema.offset + lastSubschema.textLength;
const lastDefinition = definitionsNode?.children.at(-1);
const lastDefinitionPosition = (lastDefinition)
? lastDefinition.offset + lastDefinition.textLength
: /** @type {number} */ (definitionsNode?.offset) + 1;
/** @type {CodeAction} */
const codeAction = {
title: `Extract '${newDefName}' to ${definitionsKeyword}`,
kind: CodeActionKind.RefactorExtract,
edit: {
documentChanges: [
TextDocumentEdit.create({ uri: textDocument.uri, version: null }, [
{
range: range,
newText: `{ "$ref": "#/${definitionsKeyword}/${newDefName}" }`
},
definitionsNode
? withFormatting(schemaDocument.textDocument, {
range: {
start: schemaDocument.textDocument.positionAt(lastDefinitionPosition),
end: schemaDocument.textDocument.positionAt(lastDefinitionPosition)
},
newText: lastDefinition ? `,\n"${newDefName}": ${extractedDef}` : `\n"${newDefName}": ${extractedDef}`
}, settings)
: withFormatting(schemaDocument.textDocument, {
range: {
start: schemaDocument.textDocument.positionAt(lastSubschemaPosition),
end: schemaDocument.textDocument.positionAt(lastSubschemaPosition)
},
newText: `,\n"${definitionsKeyword}": {\n"${newDefName}": ${extractedDef}\n}`
}, settings)
])
]
}
};

return [codeAction];
});
}
}
Loading