# 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'\2', line)
line = BASIC_LINK_RE2.sub(r'\3', line)
line = BASIC_LINK_RE3.sub(r'', line)
line = BASIC_LINK_RE4.sub(r'', line)
return line
def lines_to_doc_comments(lines: List[str]) -> List[str]:
if not lines:
return []
return (
["/// "]
+ [f"/// {_fix_links(line)}" for line in lines if not line.startswith("@")]
+ ["/// "]
)
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)))