Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ /*!
++ \internal
++ \page lspgenerate.html
++
++ \title Generate Language Server Protocol
++
++ The language server protocol is largely generated from the
++ specification
++ src/languageserver/3rdparty/metaModel.json
++ You can obtain the specification by searching for "meta model" on the
++ LSP website, and by removing invisible characters for the qt sanity
++ bot by doing
++ \badcode
++ iconv -f utf-8 -t ascii//IGNORE metaModelDirty.json > metaModel.json
++ \endcode
++ To generate the C++ classes open a terminal in this folder
++ (src/languageserver) and run:
++ \badcode
++ npm install
++ npm run generate
++ \endcode
++
++ It generates:
++
++ src/languageserver/qlanguageserverspectypes_p.h:
++ Types defined in the protocol are defined as C++ POD objects in the
++ QLspSpecification namespace, that implement the walk template for
++ serialization/deserialization, enmuerations are also defined, along
++ with helpers to ensure their conversion to json is what defined in
++ the specification (some enumeration should be stored as numbers,
++ other as lowercase string, other as string equal to the enumeration
++ constant).
++
++ src/languageserver/qlanguageserverspec_p.h
++ Includes qlanguageserverspectypes_p.h, and defines:
++ * the Capabilites one has to check/define, and aliases for the types
++ defining them in the namespace ClientCapabilitiesInfo for the
++ client ones, and in ServerCapabilitiesInfo for the server side.
++ * in Requests the method and alias for the paramenters of the
++ requests
++ * in Notifications the same for notifications
++ * in Responses, aliases for the types of the responses or partial
++ responses
++ * finally it defines RequestParams and NotificationsParams as
++ variants able to keep any request or notification parameters
++
++ src/languageserver/qlanguageservergen_p.h
++ Defines the ProtocolGen class that defines the typed LSP protocol:
++ * requestXX or notifyXX methods to preform requests/send
++ notifications
++ * registerXXRequestHandler methods to handle requests
++ * registerXXNotificationHandler methods to handle notifications
++
++ src/languageserver/qlanguageservergen_p_p.h
++ Declaration of QLanguageServeGenPrivate.
++
++ src/languageserver/qlanguageservergen.cpp
++ Implementation of QLanguageServeGen and QLanguageServeGenPrivate.
++
++ src/languageserver/qlspnotifysignals_p.h An object that emits signals
++ for notifications, this way one can easily have multiple handlers
++ for the same notification, by simply connecting the signal multiple
++ times.
++
++ The goal is to keep the generated part simple, that is the reason when
++ possible setup/tweaks are done in other non generated files:
++ src/languageserver/qlanguageserverprespectypes_p.h:
++ setup for qlanguageserverspectypes_p.h and qlanguageserverspec_p.h
++ src/languageserver/qlanguageserverbase_p.h and qlanguageserverbase.cpp:
++ setup for qlanguageservergen* (base class)
++ src/languageserver/qlanguageserverprotocol_p.h, qlanguageserverprotocol_p_p.h and
++ qlanguageserverprotocol.cpp:
++ Define QLanguageServerProtocol the main "user facing" class built on
++ the top of qlanguageservergen*
++ */
++ import * as ts from "typescript";
++
++ var globalBaseClass = "";
++
++ function stringLiteral(text: string)
++ {
++ return "QLatin1String(\"" + text + "\")";
++ }
++
++ const builtinTypes: { [key: string]: string } = {
++ "string" : "QByteArray",
++ "number" : "int",
++ "decimal" : "double",
++ "integer" : "int", // technically is qint32
++ "uinteger" : "unsigned int", // Note that it can't hold numbers up to 2^32-1, just up to 2ˆ31-1.
++ "boolean" : "bool",
++ "any" : "QJsonValue",
++ "unknown" : "QJsonValue",
++ "uri" : "QByteArray",
++ "DocumentUri" : "QByteArray",
++ "URI" : "QByteArray",
++ "Array" : "QList",
++ "null" : "std::nullptr_t",
++ "object" : "QJsonObject",
++ // Use QJson(Value|Object|Array) to represent LSP(Any|Object|Array). It
++ // turned out that implementing our own versions of them comes with lots
++ // of challenges: qt container types do not support the recursive
++ // inheritance, which leads to weird compile errors like QTBUG-146007 or
++ // QTBUG-146563. Also, to make a custom LSPAny class usable you have to
++ // implement methods to get the underlying type (string, int, ...), which
++ // is exactly what QJsonValue offers.
++ "LSPAny" : "QJsonValue",
++ "LSPObject" : "QJsonObject",
++ "LSPArray" : "QJsonArray",
++ "[number, number]" : "std::pair<int,int>",
++ // For enumerations in typescript single numbers can be types,
++ // namely a type containing exactly that number.
++ // That allows to also get the exact bit mask, and generally have more control.
++ // We just consider them ints.
++ "0" : "int",
++ "1" : "int",
++ "2" : "int",
++ "3" : "int",
++ "4" : "int"
++ };
++
++ const specialStructs = {
++ "Message" : null, // non templatized top level
++ "RequestMessage" : null, // non templatized top level
++ "ResponseMessage" : null, // non templatized top level
++ "NotificationMessage" : null, // non templatized top level
++ "SelectionRange" : null, // recursive reference
++ };
++
++ const specialAliases = {
++ "MarkedString" : null, // already defined by hand
++ "LSPAny" : null, // recursive reference
++ "LSPObject" : null, // recursive reference
++ "LSPArray" : null, // recursive reference
++ }
++
++ const specialEnums = {
++ "ErrorCodes" : null
++ }
++
++ interface PatchedStructMembers {
++ memberCodeByName: Map<string, string>, dependencies: string[],
++ }
++
++ const patchedStructMembers = new Map<string, PatchedStructMembers>([
++ [
++ "ProgressParams", {
++ "memberCodeByName" : new Map<string, string>([ [
++ "value",
++ "\n std::variant<WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd> %1 = {};"
++ ] ]),
++ "dependencies" :
++ [ "WorkDoneProgressBegin", "WorkDoneProgressReport", "WorkDoneProgressEnd" ]
++ }
++ ],
++ // documentChanges has a weird type, so simplify it
++ [
++ "WorkspaceEdit", {
++ "memberCodeByName" : new Map<string, string>([ [
++ "documentChanges",
++ `using DocumentChange = std::variant<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>;
++ std::optional<QList<DocumentChange>> %1 = {};`
++ ] ]),
++ "dependencies" : [ "TextDocumentEdit", "CreateFile", "RenameFile", "DeleteFile" ]
++ }
++ ],
++ ]);
++
++ var postStruct: { [key: string]: string } = {
++ "DocumentFilter" : "using DocumentSelector = QList<DocumentFilter>;\n\n",
++ "Range" : `
++ class Q_LANGUAGESERVER_EXPORT SelectionRange
++ {
++ public:
++ SelectionRange() = default;
++ SelectionRange(const Range &r):
++ range(r)
++ {}
++ SelectionRange(const SelectionRange &o):
++ range(o.range)
++ {
++ if (o.parent)
++ parent = std::make_unique<SelectionRange>(*o.parent);
++ }
++ SelectionRange &operator=(const SelectionRange &o) {
++ range = o.range;
++ if (o.parent)
++ parent = std::make_unique<SelectionRange>(*o.parent);
++ return *this;
++ }
++ SelectionRange(SelectionRange &&) noexcept = default;
++ SelectionRange& operator=(SelectionRange &&) noexcept = default;
++
++ Range range = {};
++ std::unique_ptr<SelectionRange> parent;
++
++ template <typename W> void walk(W &w) {
++ field(w, "range", range);
++ field(w, "parent", parent);
++ }
++ };
++
++ class Q_LANGUAGESERVER_EXPORT RangePlaceHolder
++ {
++ public:
++ Range range = {};
++ QByteArray placeholder = {};
++
++ template <typename W> void walk(W &w) {
++ field(w, "range", range);
++ field(w, "placeholder", placeholder);
++ }
++ };
++
++ class Q_LANGUAGESERVER_EXPORT DefaultBehaviorStruct
++ {
++ public:
++ bool defaultBehavior = {};
++
++ template <typename W> void walk(W &w) {
++ field(w, "defaultBehavior", defaultBehavior);
++ }
++ };
++
++ `
++ };
++
++ function upperCase(type: string): string
++ {
++ return type.substr(0, 1).toUpperCase() + type.substr(1);
++ }
++
++ function generateEnum(e: metaModel.Enumeration): string
++ {
++ if (e.name in specialEnums)
++ return "";
++
++ let output: string = "enum class " + e.name + "\n{\n";
++ output += e.values.map(function(entry: metaModel.EnumerationEntry) {
++ let value =
++ (typeof entry.value == "number") ? (" = " + (+entry.value)) : "";
++ return " " + upperCase(entry.name) + value;
++ })
++ .join(",\n");
++ return output + "\n};\nQ_ENUM_NS(" + e.name + ")\n\n";
++ }
++
++ function generateStringAccessors(e: metaModel.Enumeration): string
++ {
++ let output: string = "";
++ const enumName = namify(e.name);
++ output = "template<>\n";
++ output += "inline QString enumToString<QLspSpecification::" + enumName
++ + ">(QLspSpecification::" + enumName + " value)\n";
++ output += "{\n";
++ output += " switch (value) {\n"
++ output += e.values.map(function(member: metaModel.EnumerationEntry) {
++ return " case QLspSpecification::" + enumName
++ + "::" + namify(member.name) + ": return "
++ + stringLiteral(<string>member.value) + ";\n";
++ })
++ .join("");
++ output += " default: return QString::number(int(value));\n";
++ output += " }\n"
++ output += "}\n\n";
++ output += "template<>\n";
++ output += "inline QLspSpecification::" + enumName
++ + " enumFromString<QLspSpecification::" + enumName + ">(const QString &string)\n";
++ output += "{\n";
++ output += e.values.map(function(member: metaModel.EnumerationEntry) {
++ return " if (string.compare(" + stringLiteral(<string>member.value)
++ + ", Qt::CaseInsensitive) == 0)\n" +
++ " return QLspSpecification::" + enumName
++ + "::" + namify(member.name) + ";\n";
++ })
++ .join(" else ");
++ output += " return QLspSpecification::" + enumName + "{};\n";
++ output += "}\n\n";
++ return output;
++ }
++
++ function generateNumberAccessors(e: metaModel.Enumeration): string
++ {
++ let output: string = "";
++ output += "template<>\n";
++ output += "inline QString enumToString<QLspSpecification::" + e.name
++ + ">(QLspSpecification::" + e.name + " value)\n";
++ output += "{\n";
++ output += " return enumToIntString<QLspSpecification::" + e.name + ">(value);";
++ output += "}\n\n";
++
++ return output;
++ }
++
++ let typeToCppCache: Map<metaModel.Type, string> = new Map();
++ // Returns the C++ type name of a metamodel type. Use literalObjectName to create literal objects on demand if needed.
++ // Transforms std::variant<T, null> into std::optional<T>.
++ function typeToCppType(type: metaModel.Type,
++ literalObjectName: ((s: metaModel.StructureLiteral) => string)|undefined):
++ string
++ {
++ const cachedResult = typeToCppCache.get(type);
++ if (cachedResult)
++ return cachedResult
++ const result = typeToCppTypeImpl(type, literalObjectName);
++ typeToCppCache.set(type, result);
++ return result;
++ }
++
++ // squash variant of object literals that only differ in their optional properties
++ function squashType(types: metaModel.Type[]): metaModel.Type|undefined
++ {
++ if (!types.every(x => x.kind == "literal"))
++ return;
++ const firstType = types[0];
++ const otherTypes = types.slice(1);
++ if (!otherTypes.every(x => x.value.properties.length == firstType.value.properties.length))
++ return;
++
++ if (!otherTypes.every(x => x.value.properties.every(
++ (p, i) => p.name == firstType.value.properties[i].name)))
++ return;
++
++ let optionalProperties = new Set<String>();
++ const collectOptionalProperties = (p: metaModel.Property) => {
++ if (p.optional)
++ optionalProperties.add(p.name);
++ };
++ types.forEach(t => t.value.properties.forEach(collectOptionalProperties));
++
++ firstType.value.properties.forEach(p => {
++ if (optionalProperties.has(p.name))
++ p.optional = true;
++ });
++ return firstType;
++ }
++
++ function typeToCppTypeImpl(
++ type: metaModel.Type,
++ literalObjectName: ((s: metaModel.StructureLiteral) => string)|undefined): string
++ {
++ if (!type)
++ return "std::nullptr_t";
++ switch (type.kind) {
++ case "base":
++ if (type.name in builtinTypes)
++ return builtinTypes[type.name];
++ case "reference":
++ const name = (<metaModel.ReferenceType>type).name;
++ if (name in builtinTypes)
++ return builtinTypes[type.name];
++ return name
++ case "array":
++ return `QList<${typeToCppType((<metaModel.ArrayType>type).element, literalObjectName)}>`;
++ case "map":
++ return `QMap<${typeToCppType((<metaModel.MapType>type).key, literalObjectName)}, ${
++ typeToCppType((<metaModel.MapType>type).value, literalObjectName)}>`;
++ case "and":
++ throw new Error(
++ "and types are not supported in the current LSP specification, if you see this error it means the specification has changed and the code needs to be updated");
++ case "or":
++ let orTypes = (<metaModel.OrType>type).items;
++
++ // if there is a "null" type it should be the first entry of the variant
++ orTypes = orTypes.sort((a, b) => {
++ const aIsNull = a.kind === "base" && a.name === "null" ? 0 : 1;
++ const bIsNull = b.kind === "base" && b.name === "null" ? 0 : 1;
++ return aIsNull - bIsNull;
++ });
++
++ // squash variant of object literals that only differ in their optional properties
++ const squashed = squashType(orTypes)
++ if (squashed)
++ {
++ const result = typeToCppTypeImpl(squashed, literalObjectName);
++ return result;
++ }
++
++ // don't create variants if only one option is left.
++ if (orTypes.length == 1) {
++ const result = typeToCppType(orTypes[0], literalObjectName);
++ return result;
++ }
++
++ const result =
++ `std::variant<${orTypes.map(x => typeToCppType(x, literalObjectName)).join(", ")}>`;
++ return result;
++ case "tuple":
++ return `std::tuple<${
++ (<metaModel.TupleType>type)
++ .items.map(x => typeToCppType(x, literalObjectName))
++ .join(", ")}>`;
++ case "literal":
++ if (literalObjectName)
++ return literalObjectName((<metaModel.StructureLiteralType>type).value)
++ return "QJsonObject";
++ case "stringLiteral":
++ return "QByteArray";
++ case "integerLiteral":
++ return "int";
++ case "booleanLiteral":
++ return "bool";
++ }
++ }
++
++ // collect dependencies and add them to result. All strings added to result are typenames that have to be defined
++ // before type's C++ definition can be generated. Otherwise, the compiler will complain about unknown types that are
++ // actually defined later in the generated .cpp file.
++ function collectDependencies(type: metaModel.Type, result: string[]): void
++ {
++ switch (type.kind) {
++ case "base":
++ case "stringLiteral":
++ case "integerLiteral":
++ case "booleanLiteral":
++ return;
++ case "reference":
++ const name = (<metaModel.ReferenceType>type).name;
++ if (name in builtinTypes)
++ return;
++ result.push((<metaModel.ReferenceType>type).name);
++ return;
++ case "array":
++ return collectDependencies((<metaModel.ArrayType>type).element, result);
++ case "map":
++ collectDependencies((<metaModel.MapType>type).key, result);
++ collectDependencies((<metaModel.MapType>type).value, result);
++ return;
++ case "and":
++ throw new Error(
++ "and types are not supported in the current LSP specification, if you see this error it means the specification has changed and the code needs to be updated");
++ case "or":
++ case "tuple":
++ const orType = <metaModel.OrType|metaModel.TupleType>type;
++ orType.items.forEach(x => collectDependencies(x, result));
++ return;
++ case "literal":
++ const literalType = <metaModel.StructureLiteralType>type;
++ type.value.properties.forEach(x => collectDependencies(x.type, result));
++ return;
++ }
++ }
++
++ // Generate the definition code in a struct so that the definitions can be sorted dependencies first.
++ interface GeneratedClassOrAlias
++ {
++ name: string, code: string, dependencies: string[]
++ }
++ ;
++
++ function generateProperty(property: metaModel.Property, patch: PatchedStructMembers|undefined,
++ literalObjectName: ((s: metaModel.StructureLiteral) => string)|undefined):
++ string
++ {
++ if (property.type.kind == "stringLiteral")
++ return `\n static constexpr QByteArrayView kind = "${property.type.value}";`;
++
++ if (patch && patch.memberCodeByName.has(property.name)) {
++ const rType = patch!.memberCodeByName.get(property.name)!;
++ return " " + rType.replace(/%1/, property.name) + "\n";
++ }
++
++ let type = typeToCppType(property.type, literalObjectName);
++ // some types like WorkspaceFoldersInitializeParams can be empty in multiple ways - only wrap type into optional
++ if (property.optional && !type.startsWith("std::optional<"))
++ type = `std::optional<${type}>`;
++ return `\n ${type} ${property.name} = {};`
++ }
++
++ function collectDependenciesForStruct(struct: metaModel.Structure): string[]
++ {
++ let result: string[] = [];
++ if (struct.extends) {
++ struct.extends.forEach(baseType => collectDependencies(baseType, result));
++ }
++ if (struct.mixins) {
++ struct.mixins.forEach(baseType => collectDependencies(baseType, result));
++ }
++
++ // process properties
++ struct.properties.forEach(property => collectDependencies(property.type, result));
++ return result;
++ }
++
++ // generate the C++ code for a metamodel structure
++ function generateClass(struct: metaModel.Structure, indent: string,
++ seenStructures: Set<string> = new Set()): GeneratedClassOrAlias
++ {
++ let result: GeneratedClassOrAlias = {
++ name : struct.name,
++ code : "",
++ dependencies : collectDependenciesForStruct(struct),
++ };
++
++ seenStructures.add(struct.name);
++ let output: string = indent + "class Q_LANGUAGESERVER_EXPORT " + struct.name;
++
++ // process base types
++ if (struct.extends) {
++ output += " : " + struct.extends
++ .map(baseType => "public " + (<metaModel.ReferenceType>baseType).name)
++ .join(", ");
++ }
++ if (struct.mixins) {
++ if (!struct.extends)
++ output += " : ";
++ else
++ output += ", ";
++ output +=
++ struct.mixins.map(baseType => "public " + (<metaModel.ReferenceType>baseType).name)
++ .join(", ");
++ }
++
++ // process properties
++ output += "\n";
++ const innerIndent = indent + " ";
++ output += indent + "{\n" + indent + "public:";
++
++ const patch = patchedStructMembers.get(struct.name);
++ if (patch)
++ result.dependencies.push(...patch!.dependencies);
++ output += struct.properties.map(property => generateProperty(property, patch, undefined))
++ .join("");
++ output += "\n";
++ output += "\n";
++
++ // generate walk() method
++ const usesArgument = struct.properties.length != 0 || struct.mixins || struct.extends;
++ output += `${innerIndent}template <typename W> void walk(W &${usesArgument ? "w" : ""}) {`;
++ const methodIndent = innerIndent + " ";
++ if (struct.extends)
++ output += struct.extends
++ .map(baseType => `\n${methodIndent}${typeToCppType(baseType, undefined)}::walk(w);`)
++ .join("");
++ if (struct.mixins)
++ output += struct.mixins
++ .map(baseType => `\n${methodIndent}${
++ typeToCppType(baseType, undefined)}::walk(w);`)
++ .join("");
++ struct.properties.forEach(function(member: metaModel.Property) {
++ output += "\n";
++ output += `${methodIndent}field(w, "${member.name}", ${member.name});`;
++ });
++ output += "\n";
++ output += innerIndent + "}\n";
++ output += indent + "};\n\n";
++
++ let post = postStruct[struct.name];
++ if (post)
++ output += post
++
++ result.code = output;
++ return result;
++ }
++
++ interface GeneratedTypes
++ {
++ typeDeclarations: string, enumStringConversions: string
++ }
++
++ // creates a map from metamodel type name to index, such that all dependencies of a type have a smaller index than the type itself. Used
++ // to make sure that all dependencies are generated before the type.
++ function calculateOrderingOfGeneratedClassesAndAliases(classesAndAliases: GeneratedClassOrAlias[],
++ enumByNames: { [key: string]: boolean }):
++ { [key: string]: number }
++ {
++ let result: { [key: string]: number } = { };
++ const classesAndAliasesByName = Object.fromEntries(classesAndAliases.map(a => [a.name, a]));
++
++ let breakCycles = new Set<string>();
++ let counter = 0;
++ const processDependency = function(classOrAlias: GeneratedClassOrAlias) {
++ if (classOrAlias.name in result)
++ return;
++ if (breakCycles.has(classOrAlias.name))
++ return;
++ breakCycles.add(classOrAlias.name);
++ classOrAlias.dependencies.forEach(function(dependency: string) {
++ if (dependency in enumByNames || dependency in specialEnums
++ || dependency in specialAliases || dependency in specialStructs) {
++ return;
++ }
++ processDependency(classesAndAliasesByName[dependency]);
++ });
++ const currentValue = ++counter;
++ result[classOrAlias.name] = currentValue;
++ return currentValue;
++ };
++ classesAndAliases.forEach(processDependency);
++ return result;
++ }
++
++ interface GenerateObjectLiteralHelper
++ {
++ literalBaseName: string, counter: number|null, alreadyGeneratedLiterals: GeneratedClassOrAlias[]
++ }
++
++ // generate C++ structs for anonymous literal object types found in aliases (usually literal | literal ...)
++ // This generates the C++ defintion for PrepareRenameResultVariantN in:
++ // using PrepareRenameResult =
++ // std::variant<Range,
++ // PrepareRenameResultVariant1,
++ // PrepareRenameResultVariant2>;
++ // for example.
++
++ function generateObjectLiteral(helper: GenerateObjectLiteralHelper,
++ literal: metaModel.StructureLiteral): string
++ {
++ let literalName: string = helper.literalBaseName;
++ if (helper.counter != null)
++ literalName += ++helper.counter;
++
++ let result: GeneratedClassOrAlias = { name : literalName, code : "", dependencies : [] };
++ helper.alreadyGeneratedLiterals.push(result);
++
++ result.code = "class Q_LANGUAGESERVER_EXPORT " + literalName + "\n{\npublic:";
++ const innerIndent = " ";
++
++ result.code += literal.properties
++ .map(property => generateProperty(property, undefined,
++ x => generateObjectLiteral(helper, x)))
++ .join("");
++ result.code += "\n";
++ result.code += "\n";
++
++ // generate walk() method
++ result.code += `${innerIndent}template <typename W> void walk(W &w) {`;
++ const methodIndent = innerIndent + " ";
++ if (literal.properties.length == 0) {
++ result.code += "\n";
++ result.code += `${methodIndent}Q_UNUSED(w);`;
++ } else {
++ literal.properties.forEach(function(member: metaModel.Property) {
++ result.code += "\n";
++ result.code += `${methodIndent}field(w, "${member.name}", ${member.name});`;
++ });
++ }
++ result.code += "\n";
++ result.code += innerIndent + "}\n";
++ result.code += "};\n\n";
++ collectDependencies({ kind : "literal", value : literal }, result.dependencies);
++ return literalName;
++ }
++
++ // generate C++ code for a metamodel type alias, appends it to result
++ function generateAlias(alias: metaModel.TypeAlias, result: GeneratedClassOrAlias[])
++ {
++ // generate normal structs for alias that are defined as literals
++ if (alias.type.kind == "literal") {
++ let helper: GenerateObjectLiteralHelper = {
++ literalBaseName : alias.name,
++ counter : null,
++ alreadyGeneratedLiterals : result,
++ };
++ typeToCppType(alias.type, x => generateObjectLiteral(helper, x));
++ return;
++ }
++ if (alias.type.kind == "or") {
++ const squashed = squashType(alias.type.items);
++ if (squashed) {
++ let helper: GenerateObjectLiteralHelper = {
++ literalBaseName : alias.name,
++ counter : null,
++ alreadyGeneratedLiterals : result,
++ };
++ typeToCppType(squashed, x => generateObjectLiteral(helper, x));
++ return;
++ }
++ }
++
++ let helper: GenerateObjectLiteralHelper = {
++ literalBaseName : alias.name + "Variant",
++ counter : 0,
++ alreadyGeneratedLiterals : result,
++ };
++ let generatedAlias: GeneratedClassOrAlias = { name : alias.name, code : "", dependencies : [] };
++ generatedAlias.code = "using " + alias.name + " = "
++ + typeToCppType(alias.type, x => generateObjectLiteral(helper, x)) + ";\n";
++ collectDependencies(alias.type, generatedAlias.dependencies);
++ result.push(generatedAlias);
++ }
++ function stringCompare(a: string, b: string): number
++ {
++ if (a == b)
++ return 0;
++ return a < b ? -1 : 1;
++ }
++
++ /** Generate code for all classes in a set of .ts files */
++ function generate(protoData: metaModel.MetaModel): GeneratedTypes
++ {
++ let typeDeclarations: GeneratedClassOrAlias[] = [];
++ let enumStringConversions: string = "";
++ var generated: { [key: string]: boolean } = { };
++
++ var doGenerateDeclarations = function(struct: metaModel.Structure) {
++ if (generated[struct.name] !== undefined)
++ return;
++
++ if (struct.name in specialStructs)
++ return;
++ typeDeclarations.push(generateClass(struct, ""));
++ generated[struct.name] = true;
++ };
++ var doGenerateAliases = function(alias: metaModel.TypeAlias) {
++ if (alias.name in generated)
++ return;
++ if (alias.name in specialAliases)
++ return;
++
++ generateAlias(alias, typeDeclarations);
++ generated[alias.name] = true;
++ };
++
++ protoData.typeAliases.forEach(doGenerateAliases);
++ protoData.structures.forEach(doGenerateDeclarations);
++
++ protoData.enumerations.sort((a, b) => stringCompare(a.name, b.name));
++ protoData.enumerations.forEach(function(e: metaModel.Enumeration) {
++ if (e.name in specialEnums)
++ return;
++ if (e.type.name == "string")
++ enumStringConversions += generateStringAccessors(e);
++ else
++ enumStringConversions += generateNumberAccessors(e);
++ });
++
++ const enumByNames = Object.fromEntries(protoData.enumerations.map(x => [x.name, true]));
++ typeDeclarations.sort((x, y) => stringCompare(x.name, y.name));
++ const ordering = calculateOrderingOfGeneratedClassesAndAliases(typeDeclarations, enumByNames);
++ typeDeclarations.sort((x, y) => ordering[x.name] - ordering[y.name]);
++
++ let output = "";
++ protoData.enumerations.forEach(function(e) { output += generateEnum(e); });
++
++ typeDeclarations.forEach(x => output += x.code);
++
++ return { typeDeclarations : output, enumStringConversions : enumStringConversions };
++ }
++
++ import * as metaModel from "./3rdparty/metaModel.js";
++ // some types in metaModel.json were patched by hand, see 3rdparty/metaModel.json.patch
++ const metaModelJson = ts.sys.readFile("./3rdparty/metaModel.json");
++ const protoData = JSON.parse(metaModelJson!) as metaModel.MetaModel;
++
++ let result: GeneratedTypes = generate(protoData);
++
++ let license = ts.sys.readFile("generate.ts")!.split("\n\n")[0];
++
++ ts.sys.writeFile("qlanguageserverspectypes_p.h", `${license}
++
++ // this file was generated by the generate.ts script
++
++ #ifndef QLANGUAGESERVERSPECTYPES_P_H
++ #define QLANGUAGESERVERSPECTYPES_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtLanguageServer/qtlanguageserverglobal.h>
++ #include <QtLanguageServer/private/qlanguageserverprespectypes_p.h>
++ #include <QtJsonRpc/private/qtypedjson_p.h>
++ #include <QtCore/QByteArray>
++ #include <QtCore/QList>
++ #include <QtCore/QJsonValue>
++ #include <QtCore/QJsonObject>
++ #include <QtCore/QString>
++ #include <QtCore/QMap>
++
++ #include <optional>
++ #include <variant>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QLspSpecification {
++ Q_NAMESPACE_EXPORT(Q_LANGUAGESERVER_EXPORT)
++
++ enum class ErrorCodes {
++ // Defined by JSON RPC
++ ParseError = -32700,
++ InvalidRequest = -32600,
++ MethodNotFound = -32601,
++ InvalidParams = -32602,
++ InternalError = -32603,
++
++ jsonrpcReservedErrorRangeStart = -32099,
++ /** @deprecated use jsonrpcReservedErrorRangeStart */
++ serverErrorStart = jsonrpcReservedErrorRangeStart,
++
++ ServerNotInitialized = -32002,
++ UnknownErrorCode = -32001,
++
++ jsonrpcReservedErrorRangeEnd = -32000,
++ /** @deprecated use jsonrpcReservedErrorRangeEnd */
++ serverErrorEnd = jsonrpcReservedErrorRangeEnd,
++
++ lspReservedErrorRangeStart = -32899,
++
++ ContentModified = -32801,
++ RequestCancelled = -32800,
++
++ lspReservedErrorRangeEnd = -32800
++ };
++ Q_ENUM_NS(ErrorCodes)
++
++ ${result.typeDeclarations}
++ } // namespace QLspSpecification
++
++ namespace QTypedJson {
++
++ template<>
++ inline QString enumToString<QLspSpecification::ErrorCodes>(QLspSpecification::ErrorCodes value)
++ {
++ return enumToIntString<QLspSpecification::ErrorCodes>(value);
++ }
++
++ ${result.enumStringConversions}
++ } // namespace QTypedJson
++ QT_END_NAMESPACE
++ #endif // QLANGUAGESERVERSPECTYPES_P_H
++ `);
++
++ function namify(str: string): string
++ {
++ if (str.startsWith("textDocument/"))
++ str = str.replace(/[^\/]*\//, "");
++ return str.split(/[$./ _]+/).map(upperCase).join("");
++ }
++ // naming strategy:
++ // Use parameter type name of request or notification if it ends in "Params" to name the
++ // request or notification. Otherwise, remove "/" in the method name and capitalize.
++ // Examples:
++ // * "textDocument/codeAction" has parameter type CodeActionParams => return "CodeAction"
++ // * "codeAction/resolve" has parameter type CodeAction => return "CodeActionResolve"
++ function methodNameFrom(requestOrNotification: metaModel.Request|metaModel.Notification): string
++ {
++ let methodName = namify(requestOrNotification.method);
++ if (!requestOrNotification.params)
++ return methodName;
++
++ // note: in 3.17, no notification or request has more than one parameter type
++ const type = <metaModel.Type>requestOrNotification.params;
++ if (!type || type.kind != "reference")
++ return methodName;
++ if (!type.name.endsWith("Params"))
++ return methodName
++ return namify(type.name.slice(0, type.name.length - "Params".length));
++ }
++
++ function responseType(request: metaModel.Request): string
++ {
++ if (request.partialResult) {
++ return `LSPPartialResponse<${typeToCppType(request.result, undefined)}, ${
++ typeToCppType(request.partialResult, undefined)}>`;
++ }
++ return `LSPResponse<${typeToCppType(request.result, undefined)}>`;
++ }
++ function createRequest(request: metaModel.Request): string
++ {
++ const parameterType = <metaModel.Type>request.params;
++ let name = methodNameFrom(request);
++ const indent = " ";
++
++ let result = `\n${indent}constexpr auto ${name}Method = QLatin1String("${request.method}");`;
++ result += `\n${indent}using ${name}ParamsType = ${typeToCppType(parameterType, undefined)};`;
++ result += `\n${indent}using ${name}ResultType = ${typeToCppType(request.result, undefined)};`;
++ if (request.partialResult) {
++ result += `\n${indent}using ${name}PartialResultType = ${
++ typeToCppType(request.partialResult, undefined)};
++ ${indent}using ${name}ResponseType = ${responseType(request)};`;
++ } else {
++ result += `\n${indent}using ${name}ResponseType = ${responseType(request)};`;
++ }
++ return result;
++ }
++
++ function createNotification(notification: metaModel.Notification): string
++ {
++ const name = methodNameFrom(notification);
++ return `\n constexpr auto ${name}Method = "${notification.method}";
++ using ${name}ParamsType = ${typeToCppType(<metaModel.Type>notification.params, undefined)};`;
++ }
++
++ function createInGroups<Type extends metaModel.Notification>(
++ requests: Type[], createRequestOrNotification: (a: Type) => string): string
++ {
++ return createInGroupsImpl(false, requests, createRequestOrNotification);
++ }
++ function createInGroupsWithComments<Type extends metaModel.Notification>(
++ requests: Type[], createRequestOrNotification: (a: Type) => string): string
++ {
++ return createInGroupsImpl(true, requests, createRequestOrNotification);
++ }
++
++ function createInGroupsImpl<Type extends metaModel.Notification>(
++ withComments: boolean, requests: Type[],
++ createRequestOrNotification: (a: Type) => string): string
++ {
++
++ requests.sort((a, b) => stringCompare(a.method, b.method));
++
++ let namespace: string = "";
++ let output: string = "";
++ requests.forEach(request => {
++ if (withComments) {
++ let neededNamespace = request.method;
++ if (neededNamespace != namespace) {
++ if (namespace != "")
++ output += `\n`;
++ output += `\n// C++ types for the LSP method "${neededNamespace}"`;
++ namespace = neededNamespace;
++ }
++ }
++ output += createRequestOrNotification(request);
++ });
++ if (namespace != "")
++ output += `\n`
++ return output;
++ }
++
++ function responseHandlerType(type: metaModel.Type): string
++ {
++ return type.kind == "base" && type.name == "null"
++ ? "std::function<void()>"
++ : `std::function<void(const ${typeToCppType(type, undefined)} &)>`;
++ }
++
++ function createRequestDeclaration(request: metaModel.Request): string
++ {
++ const name = methodNameFrom(request);
++ const paramsType = typeToCppType(<metaModel.Type>request.params, undefined);
++ return `\n void request${name}(const ${paramsType}&, ${
++ responseHandlerType(
++ request.result)} responseHandler, ResponseErrorHandler errorHandler = &ProtocolBase::defaultResponseErrorHandler);
++ void register${name}RequestHandler(const std::function<void(const QByteArray &, const ${
++ paramsType} &, ${responseType(request)} &&)> &handler);`;
++ }
++
++ function createRequestImplementation(request: metaModel.Request): string
++ {
++ const name = methodNameFrom(request);
++ const paramsType = typeToCppType(<metaModel.Type>request.params, undefined);
++ return `
++
++ void ProtocolGen::request${name}(const ${paramsType} &params, ${
++ responseHandlerType(request.result)} responseHandler, ResponseErrorHandler errorHandler)
++ {
++ typedRpc()->sendRequest(QByteArray(Requests::${
++ name}Method), [responseHandler = std::move(responseHandler), errorHandler = std::move(errorHandler)](const QJsonRpcProtocol::Response &response) {
++ if (response.errorCode.isDouble())
++ errorHandler(ResponseError{response.errorCode.toInt(), response.errorMessage.toUtf8(), response.data});
++ else
++ decodeAndCall<${
++ typeToCppType(request.result,
++ undefined)}>(response.data, responseHandler, errorHandler);
++ }, params);
++ }
++
++ void ProtocolGen::register${
++ name}RequestHandler(const std::function<void(const QByteArray &, const ${
++ paramsType} &, ${responseType(request)} &&)> &handler)
++ {
++ typedRpc()->registerRequestHandler<
++ QLspSpecification::Requests::${name}ParamsType,
++ QLspSpecification::Responses::${
++ name}ResponseType>(QByteArray(QLspSpecification::Requests::${name}Method), handler);
++ }`;
++ }
++
++ function createRegisterNotification(notification: metaModel.Notification): string
++ {
++ const name = methodNameFrom(notification);
++ return `
++ protocol->register${name}NotificationHandler(
++ [this, protocol](const QByteArray &method, const QLspSpecification::Notifications::${
++ name}ParamsType &params) {
++ static const QMetaMethod notificationSignal = QMetaMethod::fromSignal(&QLspNotifySignals::received${
++ name}Notification);
++ if (isSignalConnected(notificationSignal))
++ emit received${name}Notification(params);
++ else
++ protocol->handleUndispatchedNotification(method);
++ });`;
++ }
++
++ function createSignalDeclaration(notification: metaModel.Notification): string
++ {
++ const name = methodNameFrom(notification);
++ return `void received${name}Notification(const QLspSpecification::Notifications::${
++ name}ParamsType &);`;
++ }
++
++ function createSendNotificationDeclaration(notification: metaModel.Notification): string
++ {
++ const name = methodNameFrom(notification);
++ const paramsType = typeToCppType(<metaModel.Type>notification.params, undefined);
++ let output = "";
++ output += `\n void register${
++ name}NotificationHandler(const std::function<void(const QByteArray &, const ${
++ paramsType} &)> &handler);`;
++ output += `\n void notify${name}(const ${paramsType} &params);`;
++ return output;
++ }
++ function createNotificationImplementation(notification: metaModel.Notification): string
++ {
++ const name = methodNameFrom(notification);
++ const paramsType = typeToCppType(<metaModel.Type>notification.params, undefined);
++ return `
++
++ void ProtocolGen::register${
++ name}NotificationHandler(const std::function<void(const QByteArray &, const ${
++ paramsType} &)> &handler)
++ {
++ typedRpc()->registerNotificationHandler<QLspSpecification::Notifications::${name}ParamsType>(
++ QByteArray(QLspSpecification::Notifications::${name}Method), handler);
++ }
++
++ void ProtocolGen::notify${name}(const ${paramsType} &params)
++ {
++ typedRpc()->sendNotification(Notifications::${name}Method, params);
++ }`;
++ }
++
++ ts.sys.writeFile(
++ "qlanguageserverspec_p.h",
++ license
++ + `
++
++ // this file was generated by the generate.ts script
++
++ #ifndef QLANGUAGESERVERSPEC_P_H
++ #define QLANGUAGESERVERSPEC_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtLanguageServer/qtlanguageserverglobal.h>
++ #include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QLspSpecification {
++ namespace Requests {${createInGroupsWithComments(protoData.requests, createRequest)}
++ } // namespace Requests
++ // for compatibility reasons:
++ namespace Responses { using namespace Requests; }
++
++ namespace Notifications {${createInGroupsWithComments(protoData.notifications, createNotification)}
++ } // namespace Notifications
++
++ } // namespace QLspSpecification
++
++ QT_END_NAMESPACE
++
++ #endif // QLANGUAGESERVERSPEC_P_H
++ `);
++
++ ts.sys.writeFile("qlanguageservergen_p.h", license + `
++
++ // this file was generated by the generate.ts script
++
++ #ifndef QLANGUAGESERVERGEN_P_H
++ #define QLANGUAGESERVERGEN_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtLanguageServer/qtlanguageserverglobal.h>
++ #include <QtLanguageServer/private/qlanguageserverspec_p.h>
++ #include <QtLanguageServer/private/qlanguageserverbase_p.h>
++
++ #include <memory>
++ #include <functional>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QLspSpecification {
++
++ class ProtocolGenPrivate;
++
++ class Q_LANGUAGESERVER_EXPORT ProtocolGen: public ProtocolBase
++ {
++ protected:
++ ProtocolGen(std::unique_ptr<ProtocolGenPrivate> &&p);
++ public:
++ ~ProtocolGen();
++
++ // Requests
++ ${createInGroups(protoData.requests, createRequestDeclaration)}
++
++ // Notifications
++ ${createInGroups(protoData.notifications, createSendNotificationDeclaration)}
++
++ private:
++ Q_DISABLE_COPY(ProtocolGen)
++ Q_DECLARE_PRIVATE(ProtocolGen)
++ };
++
++ } // namespace QLspSpecification
++
++ QT_END_NAMESPACE
++
++ #endif // QLANGUAGESERVER_P_H
++ `);
++
++ ts.sys.writeFile(
++ "qlanguageservergen.cpp",
++ license
++ + `
++
++ // this file was generated by the generate.ts script
++
++ #include <QtLanguageServer/private/qlanguageservergen_p_p.h>
++
++ #include <QtCore/QScopeGuard>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QLspSpecification {
++
++ QByteArray ProtocolBase::requestMethodToBaseCppName(const QByteArray &method)
++ {
++ static QHash<QByteArray,QByteArray> map({${
++ protoData.requests
++ .map(r => `\n { QByteArray("${r.method}"), QByteArray("${
++ methodNameFrom(r)}") }`)
++ .join(",")}
++ });
++ return map.value(method);
++ }
++
++ QByteArray ProtocolBase::notificationMethodToBaseCppName(const QByteArray &method)
++ {
++ static QHash<QByteArray,QByteArray> map({${
++ protoData.notifications
++ .map(n => `\n { QByteArray("${n.method}"), QByteArray("${
++ methodNameFrom(n)}") }`)
++ .join(",")}
++ });
++ return map.value(method);
++ }
++
++ ProtocolGen::ProtocolGen(std::unique_ptr<ProtocolGenPrivate> &&p):
++ ProtocolBase(std::move(p))
++ {
++ }
++
++ ProtocolGen::~ProtocolGen()
++ {}
++
++ // Requests
++ ${createInGroups(protoData.requests, createRequestImplementation)}
++
++ // Notifications
++ ${createInGroups(protoData.notifications, createNotificationImplementation)}
++
++ } // namespace QLspSpecification
++
++ QT_END_NAMESPACE
++ `);
++ ts.sys.writeFile("qlspnotifysignals_p.h", license + `
++
++ // this file was generated by the generate.ts script
++
++ #ifndef QLSPNOTIFYSIGNALS_P_H
++ #define QLSPNOTIFYSIGNALS_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtLanguageServer/private/qlanguageserverprotocol_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ class Q_LANGUAGESERVER_EXPORT QLspNotifySignals: public QObject
++ {
++ Q_OBJECT
++ public:
++ QLspNotifySignals(QObject *parent = nullptr) : QObject(parent) { }
++ void registerHandlers(QLanguageServerProtocol *protocol);
++ signals:
++ ${protoData.notifications.map(createSignalDeclaration).join("\n ")}
++ };
++
++ QT_END_NAMESPACE
++
++ #endif // QLSPNOTIFYSIGNALS_P_H
++ `);
++
++ ts.sys.writeFile("qlspnotifysignals.cpp", license + `
++
++ // this file was generated by the generate.ts script
++
++ #include <QtLanguageServer/private/qlspnotifysignals_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace QLspSpecification;
++
++ void QLspNotifySignals::registerHandlers(QLanguageServerProtocol *protocol)
++ {
++ ${protoData.notifications.map(createRegisterNotification).join("\n ")}
++ }
++
++ QT_END_NAMESPACE
++ `);