Move the wasm lib, and cleanup rust directory and all references (#5585)
* git mv src/wasm-lib rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv wasm-lib to workspace Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv kcl-lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv derive docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * resolve file paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * move more shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * make yarn build:wasm work Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix scripts Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * better references Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix reference Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cargo sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix script Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix a dep Signed-off-by: Jess Frazelle <github@jessfraz.com> * sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove unused deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * Revert "remove unused deps" This reverts commit fbabdb062e275fd5cbc1476f8480a1afee15d972. * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * deps; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
708
rust/kcl-lib/src/docs/kcl_doc.rs
Normal file
708
rust/kcl-lib/src/docs/kcl_doc.rs
Normal file
@ -0,0 +1,708 @@
|
||||
use tower_lsp::lsp_types::{
|
||||
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, InsertTextFormat, MarkupContent,
|
||||
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
parsing::{
|
||||
ast::types::{Annotation, Node, NonCodeNode, NonCodeValue, VariableKind},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
pub fn walk_prelude() -> Vec<DocData> {
|
||||
let mut visitor = CollectionVisitor::default();
|
||||
visitor.visit_module("prelude").unwrap();
|
||||
visitor.result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct CollectionVisitor {
|
||||
name: String,
|
||||
result: Vec<DocData>,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl CollectionVisitor {
|
||||
fn visit_module(&mut self, name: &str) -> Result<(), String> {
|
||||
let old_name = std::mem::replace(&mut self.name, name.to_owned());
|
||||
let source = crate::modules::read_std(name).unwrap();
|
||||
let parsed = crate::parsing::parse_str(source, ModuleId::from_usize(self.id))
|
||||
.parse_errs_as_err()
|
||||
.unwrap();
|
||||
self.id += 1;
|
||||
|
||||
for (i, n) in parsed.body.iter().enumerate() {
|
||||
match n {
|
||||
crate::parsing::ast::types::BodyItem::ImportStatement(import) if !import.visibility.is_default() => {
|
||||
// Only supports glob imports for now.
|
||||
assert!(matches!(
|
||||
import.selector,
|
||||
crate::parsing::ast::types::ImportSelector::Glob(..)
|
||||
));
|
||||
match &import.path {
|
||||
crate::parsing::ast::types::ImportPath::Std { path } => {
|
||||
self.visit_module(&path[1])?;
|
||||
}
|
||||
p => return Err(format!("Unexpected import: `{p}`")),
|
||||
}
|
||||
}
|
||||
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) if !var.visibility.is_default() => {
|
||||
let qual_name = if self.name == "prelude" {
|
||||
"std::".to_owned()
|
||||
} else {
|
||||
format!("std::{}::", self.name)
|
||||
};
|
||||
let mut dd = match var.kind {
|
||||
// TODO metadata for args
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name)),
|
||||
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name)),
|
||||
};
|
||||
|
||||
// FIXME this association of metadata with items is pretty flaky.
|
||||
if i == 0 {
|
||||
dd.with_meta(&parsed.non_code_meta.start_nodes, &var.outer_attrs);
|
||||
} else if let Some(meta) = parsed.non_code_meta.non_code_nodes.get(&(i - 1)) {
|
||||
dd.with_meta(meta, &var.outer_attrs);
|
||||
}
|
||||
|
||||
self.result.push(dd);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.name = old_name;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DocData {
|
||||
Fn(FnData),
|
||||
Const(ConstData),
|
||||
}
|
||||
|
||||
impl DocData {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
DocData::Fn(f) => &f.name,
|
||||
DocData::Const(c) => &c.name,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn file_name(&self) -> String {
|
||||
match self {
|
||||
DocData::Fn(f) => f.qual_name.replace("::", "-"),
|
||||
DocData::Const(c) => format!("const_{}", c.qual_name.replace("::", "-")),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn mod_name(&self) -> String {
|
||||
let q = match self {
|
||||
DocData::Fn(f) => &f.qual_name,
|
||||
DocData::Const(c) => &c.qual_name,
|
||||
};
|
||||
q[0..q.rfind("::").unwrap()].to_owned()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn hide(&self) -> bool {
|
||||
match self {
|
||||
DocData::Fn(f) => f.properties.doc_hidden || f.properties.deprecated,
|
||||
DocData::Const(c) => c.properties.doc_hidden || c.properties.deprecated,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_completion_item(&self) -> CompletionItem {
|
||||
match self {
|
||||
DocData::Fn(f) => f.to_completion_item(),
|
||||
DocData::Const(c) => c.to_completion_item(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_signature_help(&self) -> Option<SignatureHelp> {
|
||||
match self {
|
||||
DocData::Fn(f) => Some(f.to_signature_help()),
|
||||
DocData::Const(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_meta(&mut self, meta: &[Node<NonCodeNode>], attrs: &[Node<Annotation>]) {
|
||||
match self {
|
||||
DocData::Fn(f) => f.with_meta(meta, attrs),
|
||||
DocData::Const(c) => c.with_meta(meta, attrs),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn examples(&self) -> &[String] {
|
||||
match self {
|
||||
DocData::Fn(f) => &f.examples,
|
||||
DocData::Const(c) => &c.examples,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstData {
|
||||
pub name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
pub value: Option<String>,
|
||||
pub ty: Option<String>,
|
||||
pub properties: Properties,
|
||||
|
||||
/// The summary of the function.
|
||||
pub summary: Option<String>,
|
||||
/// The description of the function.
|
||||
pub description: Option<String>,
|
||||
/// Code examples.
|
||||
/// These are tested and we know they compile and execute.
|
||||
pub examples: Vec<String>,
|
||||
}
|
||||
|
||||
impl ConstData {
|
||||
fn from_ast(var: &crate::parsing::ast::types::VariableDeclaration, mut qual_name: String) -> Self {
|
||||
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Const);
|
||||
|
||||
let (value, ty) = match &var.declaration.init {
|
||||
crate::parsing::ast::types::Expr::Literal(lit) => (
|
||||
Some(lit.raw.clone()),
|
||||
Some(match &lit.value {
|
||||
crate::parsing::ast::types::LiteralValue::Number { suffix, .. } => {
|
||||
if *suffix == NumericSuffix::None || *suffix == NumericSuffix::Count {
|
||||
"number".to_owned()
|
||||
} else {
|
||||
format!("number({suffix})")
|
||||
}
|
||||
}
|
||||
crate::parsing::ast::types::LiteralValue::String { .. } => "string".to_owned(),
|
||||
crate::parsing::ast::types::LiteralValue::Bool { .. } => "boolean".to_owned(),
|
||||
}),
|
||||
),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let name = var.declaration.id.name.clone();
|
||||
qual_name.push_str(&name);
|
||||
ConstData {
|
||||
name,
|
||||
qual_name,
|
||||
value,
|
||||
// TODO use type decl when we have them.
|
||||
ty,
|
||||
properties: Properties {
|
||||
exported: !var.visibility.is_default(),
|
||||
deprecated: false,
|
||||
doc_hidden: false,
|
||||
impl_kind: ImplKind::Kcl,
|
||||
},
|
||||
summary: None,
|
||||
description: None,
|
||||
examples: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn short_docs(&self) -> Option<String> {
|
||||
match (&self.summary, &self.description) {
|
||||
(None, None) => None,
|
||||
(None, Some(d)) | (Some(d), None) => Some(d.clone()),
|
||||
(Some(s), Some(d)) => Some(format!("{s}\n\n{d}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_completion_item(&self) -> CompletionItem {
|
||||
let mut detail = self.qual_name.clone();
|
||||
if let Some(ty) = &self.ty {
|
||||
detail.push_str(": ");
|
||||
detail.push_str(ty);
|
||||
}
|
||||
CompletionItem {
|
||||
label: self.name.clone(),
|
||||
label_details: Some(CompletionItemLabelDetails {
|
||||
detail: self.value.clone(),
|
||||
description: None,
|
||||
}),
|
||||
kind: Some(CompletionItemKind::CONSTANT),
|
||||
detail: Some(detail),
|
||||
documentation: self.short_docs().map(|s| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: s,
|
||||
})
|
||||
}),
|
||||
deprecated: Some(self.properties.deprecated),
|
||||
preselect: None,
|
||||
sort_text: None,
|
||||
filter_text: None,
|
||||
insert_text: None,
|
||||
insert_text_format: None,
|
||||
insert_text_mode: None,
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
command: None,
|
||||
commit_characters: None,
|
||||
data: None,
|
||||
tags: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnData {
|
||||
/// The name of the function.
|
||||
pub name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
/// The args of the function.
|
||||
pub args: Vec<ArgData>,
|
||||
/// The return value of the function.
|
||||
pub return_type: Option<String>,
|
||||
pub properties: Properties,
|
||||
|
||||
/// The summary of the function.
|
||||
pub summary: Option<String>,
|
||||
/// The description of the function.
|
||||
pub description: Option<String>,
|
||||
/// Code examples.
|
||||
/// These are tested and we know they compile and execute.
|
||||
pub examples: Vec<String>,
|
||||
}
|
||||
|
||||
impl FnData {
|
||||
fn from_ast(var: &crate::parsing::ast::types::VariableDeclaration, mut qual_name: String) -> Self {
|
||||
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Fn);
|
||||
let crate::parsing::ast::types::Expr::FunctionExpression(expr) = &var.declaration.init else {
|
||||
unreachable!();
|
||||
};
|
||||
let name = var.declaration.id.name.clone();
|
||||
qual_name.push_str(&name);
|
||||
FnData {
|
||||
name,
|
||||
qual_name,
|
||||
args: expr.params.iter().map(ArgData::from_ast).collect(),
|
||||
return_type: expr.return_type.as_ref().map(|t| t.recast(&Default::default(), 0)),
|
||||
properties: Properties {
|
||||
exported: !var.visibility.is_default(),
|
||||
deprecated: false,
|
||||
doc_hidden: false,
|
||||
impl_kind: ImplKind::Kcl,
|
||||
},
|
||||
summary: None,
|
||||
description: None,
|
||||
examples: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn short_docs(&self) -> Option<String> {
|
||||
match (&self.summary, &self.description) {
|
||||
(None, None) => None,
|
||||
(None, Some(d)) | (Some(d), None) => Some(d.clone()),
|
||||
(Some(s), Some(d)) => Some(format!("{s}\n\n{d}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fn_signature(&self) -> String {
|
||||
let mut signature = String::new();
|
||||
|
||||
signature.push('(');
|
||||
for (i, arg) in self.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
signature.push_str(", ");
|
||||
}
|
||||
match &arg.kind {
|
||||
ArgKind::Special => signature.push_str(&format!("@{}", arg.name)),
|
||||
ArgKind::Labelled(false) => signature.push_str(&arg.name),
|
||||
ArgKind::Labelled(true) => signature.push_str(&format!("{}?", arg.name)),
|
||||
}
|
||||
if let Some(ty) = &arg.ty {
|
||||
signature.push_str(&format!(": {ty}"));
|
||||
}
|
||||
}
|
||||
signature.push(')');
|
||||
if let Some(ty) = &self.return_type {
|
||||
signature.push_str(&format!(": {ty}"));
|
||||
}
|
||||
|
||||
signature
|
||||
}
|
||||
|
||||
fn to_completion_item(&self) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label: self.name.clone(),
|
||||
label_details: Some(CompletionItemLabelDetails {
|
||||
detail: Some(self.fn_signature()),
|
||||
description: None,
|
||||
}),
|
||||
kind: Some(CompletionItemKind::FUNCTION),
|
||||
detail: Some(self.qual_name.clone()),
|
||||
documentation: self.short_docs().map(|s| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: s,
|
||||
})
|
||||
}),
|
||||
deprecated: Some(self.properties.deprecated),
|
||||
preselect: None,
|
||||
sort_text: None,
|
||||
filter_text: None,
|
||||
insert_text: Some(self.to_autocomplete_snippet()),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
insert_text_mode: None,
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
command: None,
|
||||
commit_characters: None,
|
||||
data: None,
|
||||
tags: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn to_autocomplete_snippet(&self) -> String {
|
||||
if self.name == "loft" {
|
||||
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
|
||||
} else if self.name == "hole" {
|
||||
return "hole(${0:holeSketch}, ${1:%})${}".to_owned();
|
||||
}
|
||||
let mut args = Vec::new();
|
||||
let mut index = 0;
|
||||
for arg in self.args.iter() {
|
||||
if let Some((i, arg_str)) = arg.get_autocomplete_snippet(index) {
|
||||
index = i + 1;
|
||||
args.push(arg_str);
|
||||
}
|
||||
}
|
||||
// We end with ${} so you can jump to the end of the snippet.
|
||||
// After the last argument.
|
||||
format!("{}({})${{}}", self.name, args.join(", "))
|
||||
}
|
||||
|
||||
fn to_signature_help(&self) -> SignatureHelp {
|
||||
// TODO Fill this in based on the current position of the cursor.
|
||||
let active_parameter = None;
|
||||
|
||||
SignatureHelp {
|
||||
signatures: vec![SignatureInformation {
|
||||
label: self.name.clone(),
|
||||
documentation: self.short_docs().map(|s| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: s,
|
||||
})
|
||||
}),
|
||||
parameters: Some(self.args.iter().map(|arg| arg.to_param_info()).collect()),
|
||||
active_parameter,
|
||||
}],
|
||||
active_signature: Some(0),
|
||||
active_parameter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Properties {
|
||||
pub deprecated: bool,
|
||||
pub doc_hidden: bool,
|
||||
#[allow(dead_code)]
|
||||
pub exported: bool,
|
||||
pub impl_kind: ImplKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ImplKind {
|
||||
Kcl,
|
||||
Rust,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArgData {
|
||||
/// The name of the argument.
|
||||
pub name: String,
|
||||
/// The type of the argument.
|
||||
pub ty: Option<String>,
|
||||
/// If the argument is required.
|
||||
pub kind: ArgKind,
|
||||
/// Additional information that could be used instead of the type's description.
|
||||
/// This is helpful if the type is really basic, like "u32" -- that won't tell the user much about
|
||||
/// how this argument is meant to be used.
|
||||
pub docs: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ArgKind {
|
||||
Special,
|
||||
// Parameter is whether the arg is optional.
|
||||
// TODO should store default value if present
|
||||
Labelled(bool),
|
||||
}
|
||||
|
||||
impl ArgData {
|
||||
fn from_ast(arg: &crate::parsing::ast::types::Parameter) -> Self {
|
||||
ArgData {
|
||||
name: arg.identifier.name.clone(),
|
||||
ty: arg.type_.as_ref().map(|t| t.recast(&Default::default(), 0)),
|
||||
// Doc comments are not yet supported on parameters.
|
||||
docs: None,
|
||||
kind: if arg.labeled {
|
||||
ArgKind::Labelled(arg.optional())
|
||||
} else {
|
||||
ArgKind::Special
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn _with_meta(&mut self, _meta: &[Node<NonCodeNode>]) {
|
||||
// TODO use comments for docs (we can't currently get the comments for an argument)
|
||||
}
|
||||
|
||||
pub fn get_autocomplete_snippet(&self, index: usize) -> Option<(usize, String)> {
|
||||
match &self.ty {
|
||||
Some(s)
|
||||
if [
|
||||
"Sketch",
|
||||
"SketchSet",
|
||||
"Solid",
|
||||
"SolidSet",
|
||||
"SketchSurface",
|
||||
"SketchOrSurface",
|
||||
]
|
||||
.contains(&&**s) =>
|
||||
{
|
||||
Some((index, format!("${{{}:{}}}", index, "%")))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_param_info(&self) -> ParameterInformation {
|
||||
ParameterInformation {
|
||||
label: ParameterLabel::Simple(self.name.clone()),
|
||||
documentation: self.docs.as_ref().map(|docs| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
value: docs.clone(),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgKind {
|
||||
#[allow(dead_code)]
|
||||
pub fn required(self) -> bool {
|
||||
match self {
|
||||
ArgKind::Special => true,
|
||||
ArgKind::Labelled(opt) => !opt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ApplyMeta {
|
||||
fn apply_docs(&mut self, summary: Option<String>, description: Option<String>, examples: Vec<String>);
|
||||
fn deprecated(&mut self, deprecated: bool);
|
||||
fn doc_hidden(&mut self, doc_hidden: bool);
|
||||
fn impl_kind(&mut self, impl_kind: ImplKind);
|
||||
|
||||
fn with_meta(&mut self, meta: &[Node<NonCodeNode>], attrs: &[Node<Annotation>]) {
|
||||
for attr in attrs {
|
||||
if let Annotation {
|
||||
name: None,
|
||||
properties: Some(props),
|
||||
..
|
||||
} = &attr.inner
|
||||
{
|
||||
for p in props {
|
||||
match &*p.key.name {
|
||||
"impl" => {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
self.impl_kind(match s {
|
||||
"kcl" => ImplKind::Kcl,
|
||||
"std_rust" => ImplKind::Rust,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
}
|
||||
}
|
||||
"deprecated" => {
|
||||
if let Some(b) = p.value.literal_bool() {
|
||||
self.deprecated(b);
|
||||
}
|
||||
}
|
||||
"doc_hidden" => {
|
||||
if let Some(b) = p.value.literal_bool() {
|
||||
self.doc_hidden(b);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut comments = Vec::new();
|
||||
for m in meta {
|
||||
match &m.value {
|
||||
NonCodeValue::BlockComment { value, .. } | NonCodeValue::NewLineBlockComment { value, .. } => {
|
||||
comments.push(value)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut summary = None;
|
||||
let mut description = None;
|
||||
let mut example: Option<String> = None;
|
||||
let mut examples = Vec::new();
|
||||
for l in comments.into_iter().filter(|l| l.starts_with('/')).map(|l| {
|
||||
if let Some(ll) = l.strip_prefix("/ ") {
|
||||
ll
|
||||
} else {
|
||||
&l[1..]
|
||||
}
|
||||
}) {
|
||||
if description.is_none() && summary.is_none() {
|
||||
summary = Some(l.to_owned());
|
||||
continue;
|
||||
}
|
||||
if description.is_none() {
|
||||
if l.is_empty() {
|
||||
description = Some(String::new());
|
||||
} else {
|
||||
description = summary;
|
||||
summary = None;
|
||||
let d = description.as_mut().unwrap();
|
||||
d.push_str(l);
|
||||
d.push('\n');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if l.starts_with("```") {
|
||||
if let Some(e) = example {
|
||||
examples.push(e.trim().to_owned());
|
||||
example = None;
|
||||
} else {
|
||||
example = Some(String::new());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if let Some(e) = &mut example {
|
||||
e.push_str(l);
|
||||
e.push('\n');
|
||||
continue;
|
||||
}
|
||||
match &mut description {
|
||||
Some(d) => {
|
||||
d.push_str(l);
|
||||
d.push('\n');
|
||||
}
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
assert!(example.is_none());
|
||||
if let Some(d) = &mut description {
|
||||
if d.is_empty() {
|
||||
description = None;
|
||||
}
|
||||
}
|
||||
|
||||
self.apply_docs(
|
||||
summary.map(|s| s.trim().to_owned()),
|
||||
description.map(|s| s.trim().to_owned()),
|
||||
examples,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplyMeta for ConstData {
|
||||
fn apply_docs(&mut self, summary: Option<String>, description: Option<String>, examples: Vec<String>) {
|
||||
self.summary = summary;
|
||||
self.description = description;
|
||||
self.examples = examples;
|
||||
}
|
||||
|
||||
fn deprecated(&mut self, deprecated: bool) {
|
||||
self.properties.deprecated = deprecated;
|
||||
}
|
||||
|
||||
fn doc_hidden(&mut self, doc_hidden: bool) {
|
||||
self.properties.doc_hidden = doc_hidden;
|
||||
}
|
||||
|
||||
fn impl_kind(&mut self, _impl_kind: ImplKind) {}
|
||||
}
|
||||
|
||||
impl ApplyMeta for FnData {
|
||||
fn apply_docs(&mut self, summary: Option<String>, description: Option<String>, examples: Vec<String>) {
|
||||
self.summary = summary;
|
||||
self.description = description;
|
||||
self.examples = examples;
|
||||
}
|
||||
|
||||
fn deprecated(&mut self, deprecated: bool) {
|
||||
self.properties.deprecated = deprecated;
|
||||
}
|
||||
|
||||
fn doc_hidden(&mut self, doc_hidden: bool) {
|
||||
self.properties.doc_hidden = doc_hidden;
|
||||
}
|
||||
|
||||
fn impl_kind(&mut self, impl_kind: ImplKind) {
|
||||
self.properties.impl_kind = impl_kind;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let result = walk_prelude();
|
||||
for d in result {
|
||||
if let DocData::Const(d) = d {
|
||||
if d.name == "PI" {
|
||||
assert!(d.value.unwrap().starts_with('3'));
|
||||
assert_eq!(d.ty, Some("number".to_owned()));
|
||||
assert_eq!(d.qual_name, "std::math::PI");
|
||||
assert!(d.summary.is_some());
|
||||
assert!(!d.examples.is_empty());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("didn't find PI");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn test_examples() -> miette::Result<()> {
|
||||
let std = walk_prelude();
|
||||
for d in std {
|
||||
for (i, eg) in d.examples().iter().enumerate() {
|
||||
let result =
|
||||
match crate::test_server::execute_and_snapshot(eg, crate::settings::types::UnitLength::Mm, None)
|
||||
.await
|
||||
{
|
||||
Err(crate::errors::ExecError::Kcl(e)) => {
|
||||
return Err(miette::Report::new(crate::errors::Report {
|
||||
error: e.error,
|
||||
filename: format!("{}{i}", d.name()),
|
||||
kcl_source: eg.to_string(),
|
||||
}));
|
||||
}
|
||||
Err(other_err) => panic!("{}", other_err),
|
||||
Ok(img) => img,
|
||||
};
|
||||
twenty_twenty::assert_image(
|
||||
format!("tests/outputs/serial_test_example_{}{i}.png", d.file_name()),
|
||||
&result,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user