Skip to content

Commit 6031d9b

Browse files
authored
Merge pull request #3394 from github/koesie10/auto-run-generate-ruby-type-models
Auto-generate type models for Ruby
2 parents c216b52 + e408c0a commit 6031d9b

File tree

3 files changed

+125
-18
lines changed

3 files changed

+125
-18
lines changed

extensions/ql-vscode/src/model-editor/languages/models-as-data.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,34 @@ export type ModelsAsDataLanguagePredicate<T> = {
3232
readModeledMethod: ReadModeledMethod;
3333
};
3434

35+
type ParseGenerationResults = (
36+
// The path to the query that generated the results.
37+
queryPath: string,
38+
// The results of the query.
39+
bqrs: DecodedBqrs,
40+
// The language-specific predicate that was used to generate the results. This is passed to allow
41+
// sharing of code between different languages.
42+
modelsAsDataLanguage: ModelsAsDataLanguage,
43+
// The logger to use for logging.
44+
logger: BaseLogger,
45+
) => ModeledMethod[];
46+
3547
type ModelsAsDataLanguageModelGeneration = {
3648
queryConstraints: (mode: Mode) => QueryConstraints;
3749
filterQueries?: (queryPath: string) => boolean;
38-
parseResults: (
39-
// The path to the query that generated the results.
40-
queryPath: string,
41-
// The results of the query.
42-
bqrs: DecodedBqrs,
43-
// The language-specific predicate that was used to generate the results. This is passed to allow
44-
// sharing of code between different languages.
45-
modelsAsDataLanguage: ModelsAsDataLanguage,
46-
// The logger to use for logging.
47-
logger: BaseLogger,
48-
) => ModeledMethod[];
50+
parseResults: ParseGenerationResults;
51+
/**
52+
* If autoRun is not undefined, the query will be run automatically when the user starts the
53+
* model editor.
54+
*
55+
* This only applies to framework mode. Application mode will never run the query automatically.
56+
*/
57+
autoRun?: {
58+
/**
59+
* If defined, will use a custom parsing function when the query is run automatically.
60+
*/
61+
parseResults?: ParseGenerationResults;
62+
};
4963
};
5064

5165
type ModelsAsDataLanguageAccessPathSuggestions = {

extensions/ql-vscode/src/model-editor/languages/ruby/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,28 @@ export const ruby: ModelsAsDataLanguage = {
177177
"tags contain all": ["modeleditor", "generate-model", modeTag(mode)],
178178
}),
179179
parseResults: parseGenerateModelResults,
180+
autoRun: {
181+
parseResults: (queryPath, bqrs, modelsAsDataLanguage, logger) => {
182+
// Only type models are generated automatically
183+
const typePredicate = modelsAsDataLanguage.predicates.type;
184+
if (!typePredicate) {
185+
throw new Error("Type predicate not found");
186+
}
187+
188+
const filteredBqrs = Object.fromEntries(
189+
Object.entries(bqrs).filter(
190+
([key]) => key === typePredicate.extensiblePredicate,
191+
),
192+
);
193+
194+
return parseGenerateModelResults(
195+
queryPath,
196+
filteredBqrs,
197+
modelsAsDataLanguage,
198+
logger,
199+
);
200+
},
201+
},
180202
},
181203
accessPathSuggestions: {
182204
queryConstraints: (mode) => ({

extensions/ql-vscode/src/model-editor/model-editor-view.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ export class ModelEditorView extends AbstractWebview<
6767
private readonly autoModeler: AutoModeler;
6868
private readonly modelEvaluator: ModelEvaluator;
6969
private readonly languageDefinition: ModelsAsDataLanguage;
70+
// Cancellation token source that can be used for passing into long-running operations. Should only
71+
// be cancelled when the view is closed
72+
private readonly cancellationTokenSource = new CancellationTokenSource();
7073

7174
public constructor(
7275
protected readonly app: App,
@@ -86,6 +89,12 @@ export class ModelEditorView extends AbstractWebview<
8689
) {
8790
super(app);
8891

92+
this.push({
93+
dispose: () => {
94+
this.cancellationTokenSource.cancel();
95+
},
96+
});
97+
8998
this.modelingStore.initializeStateForDb(databaseItem, initialMode);
9099
this.registerToModelingEvents();
91100
this.registerToModelConfigEvents();
@@ -378,6 +387,8 @@ export class ModelEditorView extends AbstractWebview<
378387
this.setViewState(),
379388
withProgress((progress, token) => this.loadMethods(progress, token), {
380389
cancellable: true,
390+
}).then(async () => {
391+
await this.generateModeledMethodsOnStartup();
381392
}),
382393
this.loadExistingModeledMethods(),
383394
// Only load access path suggestions if the feature is enabled
@@ -482,7 +493,7 @@ export class ModelEditorView extends AbstractWebview<
482493

483494
try {
484495
if (!token) {
485-
token = new CancellationTokenSource().token;
496+
token = this.cancellationTokenSource.token;
486497
}
487498
const queryResult = await runModelEditorQueries(mode, {
488499
cliServer: this.cliServer,
@@ -533,8 +544,6 @@ export class ModelEditorView extends AbstractWebview<
533544
protected async loadAccessPathSuggestions(
534545
progress: ProgressCallback,
535546
): Promise<void> {
536-
const tokenSource = new CancellationTokenSource();
537-
538547
const mode = this.modelingStore.getMode(this.databaseItem);
539548

540549
const modelsAsDataLanguage = getModelsAsDataLanguage(this.language);
@@ -557,7 +566,7 @@ export class ModelEditorView extends AbstractWebview<
557566
queryStorageDir: this.queryStorageDir,
558567
databaseItem: this.databaseItem,
559568
progress,
560-
token: tokenSource.token,
569+
token: this.cancellationTokenSource.token,
561570
logger: this.app.logger,
562571
});
563572

@@ -588,8 +597,6 @@ export class ModelEditorView extends AbstractWebview<
588597
protected async generateModeledMethods(): Promise<void> {
589598
await withProgress(
590599
async (progress) => {
591-
const tokenSource = new CancellationTokenSource();
592-
593600
const mode = this.modelingStore.getMode(this.databaseItem);
594601

595602
const modelsAsDataLanguage = getModelsAsDataLanguage(this.language);
@@ -647,7 +654,7 @@ export class ModelEditorView extends AbstractWebview<
647654
queryStorageDir: this.queryStorageDir,
648655
databaseItem: addedDatabase ?? this.databaseItem,
649656
progress,
650-
token: tokenSource.token,
657+
token: this.cancellationTokenSource.token,
651658
});
652659
} catch (e: unknown) {
653660
void showAndLogExceptionWithTelemetry(
@@ -663,6 +670,70 @@ export class ModelEditorView extends AbstractWebview<
663670
);
664671
}
665672

673+
protected async generateModeledMethodsOnStartup(): Promise<void> {
674+
const mode = this.modelingStore.getMode(this.databaseItem);
675+
if (mode !== Mode.Framework) {
676+
return;
677+
}
678+
679+
const modelsAsDataLanguage = getModelsAsDataLanguage(this.language);
680+
const modelGeneration = modelsAsDataLanguage.modelGeneration;
681+
const autoRun = modelGeneration?.autoRun;
682+
683+
if (modelGeneration === undefined || autoRun === undefined) {
684+
return;
685+
}
686+
687+
await withProgress(
688+
async (progress) => {
689+
progress({
690+
step: 0,
691+
maxStep: 4000,
692+
message: "Generating models",
693+
});
694+
695+
const parseResults =
696+
autoRun.parseResults ?? modelGeneration.parseResults;
697+
698+
try {
699+
await runGenerateQueries({
700+
queryConstraints: modelGeneration.queryConstraints(mode),
701+
filterQueries: modelGeneration.filterQueries,
702+
parseResults: (queryPath, results) =>
703+
parseResults(
704+
queryPath,
705+
results,
706+
modelsAsDataLanguage,
707+
this.app.logger,
708+
),
709+
onResults: async (modeledMethods) => {
710+
this.addModeledMethodsFromArray(modeledMethods);
711+
},
712+
cliServer: this.cliServer,
713+
queryRunner: this.queryRunner,
714+
queryStorageDir: this.queryStorageDir,
715+
databaseItem: this.databaseItem,
716+
progress,
717+
token: this.cancellationTokenSource.token,
718+
});
719+
} catch (e: unknown) {
720+
void showAndLogExceptionWithTelemetry(
721+
this.app.logger,
722+
this.app.telemetry,
723+
redactableError(
724+
asError(e),
725+
)`Failed to auto-run generating models: ${getErrorMessage(e)}`,
726+
);
727+
}
728+
},
729+
{
730+
cancellable: false,
731+
location: ProgressLocation.Window,
732+
title: "Generating models",
733+
},
734+
);
735+
}
736+
666737
private async generateModeledMethodsFromLlm(
667738
packageName: string,
668739
methodSignatures: string[],

0 commit comments

Comments
 (0)