summaryrefslogtreecommitdiffstats
path: root/generator/plugins/dotnet/dotnet_helpers.py
diff options
context:
space:
mode:
Diffstat (limited to 'generator/plugins/dotnet/dotnet_helpers.py')
-rw-r--r--generator/plugins/dotnet/dotnet_helpers.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/generator/plugins/dotnet/dotnet_helpers.py b/generator/plugins/dotnet/dotnet_helpers.py
new file mode 100644
index 0000000..2eb357a
--- /dev/null
+++ b/generator/plugins/dotnet/dotnet_helpers.py
@@ -0,0 +1,215 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+import re
+from typing import List, Optional, Union
+
+from generator import model
+
+BASIC_LINK_RE = re.compile(r"{@link +(\w+) ([\w ]+)}")
+BASIC_LINK_RE2 = re.compile(r"{@link +(\w+)\.(\w+) ([\w \.`]+)}")
+BASIC_LINK_RE3 = re.compile(r"{@link +(\w+)}")
+BASIC_LINK_RE4 = re.compile(r"{@link +(\w+)\.(\w+)}")
+PARTS_RE = re.compile(r"(([a-z0-9])([A-Z]))")
+
+
+def _fix_links(line: str) -> str:
+ line = BASIC_LINK_RE.sub(r'<see cref="\1">\2</see>', line)
+ line = BASIC_LINK_RE2.sub(r'<see cref="\1.\2">\3</see>', line)
+ line = BASIC_LINK_RE3.sub(r'<see cref="\1" />', line)
+ line = BASIC_LINK_RE4.sub(r'<see cref="\1.\2" />', line)
+ return line
+
+
+def lines_to_doc_comments(lines: List[str]) -> List[str]:
+ if not lines:
+ return []
+
+ return (
+ ["/// <summary>"]
+ + [f"/// {_fix_links(line)}" for line in lines if not line.startswith("@")]
+ + ["/// </summary>"]
+ )
+
+
+def get_parts(name: str) -> List[str]:
+ name = name.replace("_", " ")
+ return PARTS_RE.sub(r"\2 \3", name).split()
+
+
+def to_camel_case(name: str) -> str:
+ parts = get_parts(name)
+ return parts[0] + "".join([p.capitalize() for p in parts[1:]])
+
+
+def to_upper_camel_case(name: str) -> str:
+ return "".join([c.capitalize() for c in get_parts(name)])
+
+
+def lsp_method_to_name(method: str) -> str:
+ if method.startswith("$"):
+ method = method[1:]
+ method = method.replace("/", "_")
+ return to_upper_camel_case(method)
+
+
+def file_header() -> List[str]:
+ return [
+ "// Copyright (c) Microsoft Corporation. All rights reserved.",
+ "// Licensed under the MIT License.",
+ "// ",
+ "// THIS FILE IS AUTOGENERATED, DO NOT MODIFY IT",
+ "",
+ ]
+
+
+def namespace_wrapper(
+ namespace: str, imports: List[str], lines: List[str]
+) -> List[str]:
+ indent = " " * 4
+ return (
+ file_header()
+ + imports
+ + [""]
+ + ["namespace " + namespace + " {"]
+ + [(f"{indent}{line}" if line else line) for line in lines]
+ + ["}", ""]
+ )
+
+
+def get_doc(doc: Optional[str]) -> str:
+ if doc:
+ return lines_to_doc_comments(doc.splitlines(keepends=False))
+ return []
+
+
+def get_special_case_class_name(name: str) -> str:
+ # This is because C# does not allow class name and property name to be the same.
+ # public class Command{ public string Command { get; set; }} is not valid.
+ if name == "Command":
+ return "CommandAction"
+ return name
+
+
+def get_special_case_property_name(name: str) -> str:
+ if name == "string":
+ return "stringValue"
+ if name == "int":
+ return "intValue"
+ if name == "event":
+ return "eventArgs"
+ if name == "params":
+ return "paramsValue"
+ return name
+
+
+def class_wrapper(
+ type_def: Union[model.Structure, model.Notification, model.Request],
+ inner: List[str],
+ derived: Optional[str] = None,
+ class_attributes: Optional[List[str]] = None,
+ is_record=True,
+) -> List[str]:
+ if hasattr(type_def, "name"):
+ name = get_special_case_class_name(type_def.name)
+ else:
+ raise ValueError(f"Unknown type: {type_def}")
+
+ rec_or_cls = "record" if is_record else "class"
+ lines = (
+ get_doc(type_def.documentation)
+ + generate_extras(type_def)
+ + (class_attributes if class_attributes else [])
+ + [
+ "[DataContract]",
+ f"public {rec_or_cls} {name}: {derived}"
+ if derived
+ else f"public {rec_or_cls} {name}",
+ "{",
+ ]
+ )
+ lines += indent_lines(inner)
+ lines += ["}", ""]
+ return lines
+
+
+def property_wrapper(prop_def: model.Property, content: List[str]) -> List[str]:
+ lines = (get_doc(prop_def.documentation) + generate_extras(prop_def) + content,)
+ lines += indent_lines(content)
+ return lines
+
+
+def indent_lines(lines: List[str], indent: str = " " * 4) -> List[str]:
+ return [(f"{indent}{line}" if line else line) for line in lines]
+
+
+def cleanup_str(text: str) -> str:
+ return text.replace("\r", "").replace("\n", "")
+
+
+def get_deprecated(text: Optional[str]) -> Optional[str]:
+ if not text:
+ return None
+
+ lines = text.splitlines(keepends=False)
+ for line in lines:
+ if line.startswith("@deprecated"):
+ return line.replace("@deprecated", "").strip()
+ return None
+
+
+def generate_extras(
+ type_def: Union[
+ model.Enum,
+ model.EnumItem,
+ model.Property,
+ model.TypeAlias,
+ model.Structure,
+ model.Request,
+ model.Notification,
+ ]
+) -> List[str]:
+ deprecated = get_deprecated(type_def.documentation)
+ extras = []
+ if type_def.deprecated:
+ extras += [f'[Obsolete("{cleanup_str(type_def.deprecated)}")]']
+ elif deprecated:
+ extras += [f'[Obsolete("{cleanup_str(deprecated)}")]']
+ if type_def.proposed:
+ extras += [f"[Proposed]"]
+ if type_def.since:
+ extras += [f'[Since("{cleanup_str(type_def.since)}")]']
+
+ if hasattr(type_def, "messageDirection"):
+ if type_def.since:
+ extras += [
+ f"[Direction(MessageDirection.{to_upper_camel_case(type_def.messageDirection)})]"
+ ]
+
+ return extras
+
+
+def get_usings(types: List[str]) -> List[str]:
+ usings = []
+
+ for t in ["DataMember", "DataContract"]:
+ if t in types:
+ usings.append("using System.Runtime.Serialization;")
+
+ for t in ["JsonConverter", "JsonConstructor", "JsonProperty", "NullValueHandling"]:
+ if t in types:
+ usings.append("using Newtonsoft.Json;")
+
+ for t in ["JToken", "JObject", "JArray"]:
+ if t in types:
+ usings.append("using Newtonsoft.Json.Linq;")
+
+ for t in ["List", "Dictionary"]:
+ if t in types:
+ usings.append("using System.Collections.Generic;")
+
+ for t in ["ImmutableArray", "ImmutableDictionary"]:
+ if t in types:
+ usings.append("using System.Collections.Immutable;")
+
+ return sorted(list(set(usings)))