From fed30b6fed2be60a9856df3f28093fcb0ca33f80 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 10 Apr 2024 16:46:45 +0200 Subject: [PATCH] Add auto-model generation mode without separate file --- .../model-editor/languages/models-as-data.ts | 25 +++++- .../src/model-editor/languages/ruby/index.ts | 29 ++++++- .../src/model-editor/model-editor-view.ts | 81 +++++++++++++------ 3 files changed, 105 insertions(+), 30 deletions(-) diff --git a/extensions/ql-vscode/src/model-editor/languages/models-as-data.ts b/extensions/ql-vscode/src/model-editor/languages/models-as-data.ts index 050b23f0a..87d6059d5 100644 --- a/extensions/ql-vscode/src/model-editor/languages/models-as-data.ts +++ b/extensions/ql-vscode/src/model-editor/languages/models-as-data.ts @@ -105,15 +105,34 @@ type ParseResultsToYaml = ( logger: BaseLogger, ) => ModelExtension[]; +export enum AutoModelGenerationType { + /** + * Auto model generation is disabled and will not be run. + */ + Disabled = "disabled", + /** + * The models are generated to a separate file (suffixed with .model.generated.yml). + */ + SeparateFile = "separateFile", + /** + * The models are added as a model in the model editor, but are not automatically saved. + * The user can view them and choose to save them. + */ + Models = "models", +} + type ModelsAsDataLanguageAutoModelGeneration = { queryConstraints: (mode: Mode) => QueryConstraints; filterQueries?: (queryPath: string) => boolean; + /** + * This function is only used when type is `separateFile`. + */ parseResultsToYaml: ParseResultsToYaml; /** - * By default, auto model generation is enabled for all modes. This function can be used to - * override that behavior. + * This function is only used when type is `models`. */ - enabled?: (context: GenerationContext) => boolean; + parseResults: ParseGenerationResults; + type: (context: GenerationContext) => AutoModelGenerationType; }; type ModelsAsDataLanguageAccessPathSuggestions = { diff --git a/extensions/ql-vscode/src/model-editor/languages/ruby/index.ts b/extensions/ql-vscode/src/model-editor/languages/ruby/index.ts index f995b034f..3e14ce6df 100644 --- a/extensions/ql-vscode/src/model-editor/languages/ruby/index.ts +++ b/extensions/ql-vscode/src/model-editor/languages/ruby/index.ts @@ -1,4 +1,5 @@ import type { ModelsAsDataLanguage } from "../models-as-data"; +import { AutoModelGenerationType } from "../models-as-data"; import { sharedExtensiblePredicates, sharedKinds } from "../shared"; import { Mode } from "../../shared/mode"; import { parseGenerateModelResults } from "./generate"; @@ -209,9 +210,33 @@ export const ruby: ModelsAsDataLanguage = { }, ]; }, + parseResults: (queryPath, bqrs, modelsAsDataLanguage, logger, context) => { + // Only parse type models when automatically generating models + const typePredicate = modelsAsDataLanguage.predicates.type; + if (!typePredicate) { + throw new Error("Type predicate not found"); + } + + const typeTuples = bqrs[typePredicate.extensiblePredicate]; + if (!typeTuples) { + return []; + } + + return parseGenerateModelResults( + queryPath, + { + [typePredicate.extensiblePredicate]: typeTuples, + }, + modelsAsDataLanguage, + logger, + context, + ); + }, // Only enabled for framework mode when type models are hidden - enabled: ({ mode, config }) => - mode === Mode.Framework && !config.showTypeModels, + type: ({ mode, config }) => + mode === Mode.Framework && !config.showTypeModels + ? AutoModelGenerationType.SeparateFile + : AutoModelGenerationType.Disabled, }, accessPathSuggestions: { queryConstraints: (mode) => ({ diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index 7ccd9c89d..c7945536a 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -54,7 +54,11 @@ import { telemetryListener } from "../common/vscode/telemetry"; import type { ModelingStore } from "./modeling-store"; import type { ModelingEvents } from "./modeling-events"; import type { ModelsAsDataLanguage } from "./languages"; -import { createModelConfig, getModelsAsDataLanguage } from "./languages"; +import { + AutoModelGenerationType, + createModelConfig, + getModelsAsDataLanguage, +} from "./languages"; import { runGenerateQueries } from "./generate"; import { ResponseError } from "vscode-jsonrpc"; import { LSPErrorCodes } from "vscode-languageclient"; @@ -710,10 +714,12 @@ export class ModelEditorView extends AbstractWebview< return; } - if ( - autoModelGeneration.enabled && - !autoModelGeneration.enabled({ mode, config: this.modelConfig }) - ) { + const autoModelType = autoModelGeneration.type({ + mode, + config: this.modelConfig, + }); + + if (autoModelType === AutoModelGenerationType.Disabled) { return; } @@ -734,14 +740,37 @@ export class ModelEditorView extends AbstractWebview< queryConstraints: autoModelGeneration.queryConstraints(mode), filterQueries: autoModelGeneration.filterQueries, onResults: (queryPath, results) => { - const extensions = autoModelGeneration.parseResultsToYaml( - queryPath, - results, - modelsAsDataLanguage, - this.app.logger, - ); + switch (autoModelType) { + case AutoModelGenerationType.SeparateFile: { + const extensions = autoModelGeneration.parseResultsToYaml( + queryPath, + results, + modelsAsDataLanguage, + this.app.logger, + ); - extensionFile.extensions.push(...extensions); + extensionFile.extensions.push(...extensions); + break; + } + case AutoModelGenerationType.Models: { + const modeledMethods = autoModelGeneration.parseResults( + queryPath, + results, + modelsAsDataLanguage, + this.app.logger, + { + mode, + config: this.modelConfig, + }, + ); + + this.addModeledMethodsFromArray(modeledMethods); + break; + } + default: { + assertNever(autoModelType); + } + } }, cliServer: this.cliServer, queryRunner: this.queryRunner, @@ -761,22 +790,24 @@ export class ModelEditorView extends AbstractWebview< return; } - progress({ - step: 4000, - maxStep: 4000, - message: "Saving generated models", - }); + if (autoModelType === AutoModelGenerationType.SeparateFile) { + progress({ + step: 4000, + maxStep: 4000, + message: "Saving generated models", + }); - const fileContents = `# This file was automatically generated from ${this.databaseItem.name}. Manual changes will not persist.\n\n${modelExtensionFileToYaml(extensionFile)}`; - const filePath = join( - this.extensionPack.path, - "models", - `${this.language}${GENERATED_MODELS_SUFFIX}`, - ); + const fileContents = `# This file was automatically generated from ${this.databaseItem.name}. Manual changes will not persist.\n\n${modelExtensionFileToYaml(extensionFile)}`; + const filePath = join( + this.extensionPack.path, + "models", + `${this.language}${GENERATED_MODELS_SUFFIX}`, + ); - await outputFile(filePath, fileContents); + await outputFile(filePath, fileContents); - void this.app.logger.log(`Saved generated model file to ${filePath}`); + void this.app.logger.log(`Saved generated model file to ${filePath}`); + } }, { cancellable: false,