Some improvements to the boxed signatures in the docs (#6593)

* Show a more reasonable name in function docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix buggy docs for union types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Make types in the docs signatures into links

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-05-01 04:03:22 +12:00
committed by GitHub
parent ccd5b0272d
commit c050739f41
128 changed files with 821 additions and 731 deletions

View File

@ -461,7 +461,7 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String)
let data = json!({
"name": ty.qual_name(),
"definition": ty.alias.as_ref().map(|t| format!("type {} = {t}", ty.name)),
"definition": ty.alias.as_ref().map(|t| cleanup_sig(format!("type {} = {t}", ty.preferred_name), ty.referenced_types.iter().filter(|t| !DECLARED_TYPES.contains(&&***t)))),
"summary": ty.summary,
"description": ty.description,
"deprecated": ty.properties.deprecated,
@ -485,8 +485,6 @@ fn generate_function_from_kcl(function: &FnData, file_name: String) -> Result<()
let hbs = init_handlebars()?;
let name = function.name.clone();
let examples: Vec<serde_json::Value> = function
.examples
.iter()
@ -499,7 +497,10 @@ fn generate_function_from_kcl(function: &FnData, file_name: String) -> Result<()
"summary": function.summary,
"description": function.description,
"deprecated": function.properties.deprecated,
"fn_signature": name.clone() + &function.fn_signature(),
"fn_signature": cleanup_sig(function.preferred_name.clone() + &function.fn_signature(), function
.referenced_types
.iter()
.filter(|t| !DECLARED_TYPES.contains(&&***t))),
"tags": [],
"examples": examples,
"is_utilities": false,
@ -624,7 +625,7 @@ fn generate_function(internal_fn: Box<dyn StdLibFn>) -> Result<BTreeMap<String,
"summary": internal_fn.summary(),
"description": internal_fn.description(),
"deprecated": internal_fn.deprecated(),
"fn_signature": internal_fn.fn_signature(true),
"fn_signature": cleanup_sig(internal_fn.fn_signature(true), types.keys()),
"tags": internal_fn.tags(),
"examples": examples,
"is_utilities": internal_fn.tags().contains(&"utilities".to_string()),
@ -653,20 +654,6 @@ fn generate_function(internal_fn: Box<dyn StdLibFn>) -> Result<BTreeMap<String,
Ok(types)
}
fn cleanup_static_links(output: &str) -> String {
let mut cleaned_output = output.to_string();
// Fix the links to the types.
// Gross hack for the stupid alias types.
cleaned_output = cleaned_output.replace("TagNode", "TagDeclarator");
let link = format!("[`{}`](/docs/kcl/types#tag-declaration)", "TagDeclarator");
cleaned_output = cleaned_output.replace("`TagDeclarator`", &link);
let link = format!("[`{}`](/docs/kcl/types#tag-identifier)", "TagIdentifier");
cleaned_output = cleaned_output.replace("`TagIdentifier`", &link);
cleaned_output
}
// Fix the links to the types.
fn cleanup_type_links<'a>(output: &str, types: impl Iterator<Item = &'a String>) -> String {
let mut cleaned_output = output.to_string();
@ -675,11 +662,11 @@ fn cleanup_type_links<'a>(output: &str, types: impl Iterator<Item = &'a String>)
// TODO: This is a hack for the handlebars template being too complex.
cleaned_output = cleaned_output.replace("`[, `number`, `number`]`", "`[number, number]`");
cleaned_output = cleaned_output.replace("`[, `number`, `number`, `number`]`", "`[number, number, number]`");
cleaned_output = cleaned_output.replace("`[, `integer`, `integer`, `integer`]`", "`[integer, integer, integer]`");
// Fix the links to the types.
for type_name in types.map(|s| &**s).chain(DECLARED_TYPES) {
if type_name == "TagDeclarator" || type_name == "TagIdentifier" || type_name == "TagNode" {
if type_name == "TagDeclarator" || type_name == "TagIdentifier" || type_name == "TagNode" || type_name == "tag"
{
continue;
} else {
let link = format!("(/docs/kcl/types/{})", type_name);
@ -694,10 +681,70 @@ fn cleanup_type_links<'a>(output: &str, types: impl Iterator<Item = &'a String>)
// TODO handle union types generically rather than special casing them.
cleaned_output = cleaned_output.replace(
"`Sketch | Plane | Face`",
"[`Sketch`](/docs/kcl/types/Sketch) OR [`Plane`](/docs/kcl/types/Plane) OR [`Face`](/docs/kcl/types/Face)",
"[`Sketch`](/docs/kcl/types/Sketch) or [`Plane`](/docs/kcl/types/Plane) or [`Face`](/docs/kcl/types/Face)",
);
cleaned_output = cleaned_output.replace(
"`Axis3d | Edge`",
"[`Axis3d`](/docs/kcl/types/Axis3d) or [`Edge`](/docs/kcl/types/Edge)",
);
cleaned_output = cleaned_output.replace(
"`Axis2d | Edge`",
"[`Axis2d`](/docs/kcl/types/Axis2d) or [`Edge`](/docs/kcl/types/Edge)",
);
cleanup_static_links(&cleaned_output)
// Fix the links to the types.
// Gross hack for the stupid alias types.
cleaned_output = cleaned_output.replace("TagNode", "TagDeclarator");
let link = "[`TagDeclarator`](/docs/kcl/types#tag-declaration)";
cleaned_output = cleaned_output.replace("`TagDeclarator`", link);
let link = "[`TagIdentifier`](/docs/kcl/types#tag-identifier)";
cleaned_output = cleaned_output.replace("`TagIdentifier`", link);
cleaned_output
}
// TODO total code dup with `cleanup_type_links`, just this version doesn't have the backticks. :-(
fn cleanup_sig<'a>(input: String, types: impl Iterator<Item = &'a String>) -> String {
let mut cleaned_output = input;
// Fix the links to the types.
for type_name in types.map(|s| &**s).chain(DECLARED_TYPES) {
if type_name == "TagDeclarator" || type_name == "TagIdentifier" || type_name == "TagNode" || type_name == "tag"
{
continue;
} else {
let link = format!("(/docs/kcl/types/{})", type_name);
// Do the same for the type with brackets.
cleaned_output = cleaned_output.replace(&format!("[{}]", type_name), &format!("[[{}]]{}", type_name, link));
cleaned_output = cleaned_output.replace(type_name, &format!("[{}]{}", type_name, &link));
}
}
// TODO handle union types generically rather than special casing them.
cleaned_output = cleaned_output.replace(
"Sketch | Plane | Face",
"[Sketch](/docs/kcl/types/Sketch) | [Plane](/docs/kcl/types/Plane) | [Face](/docs/kcl/types/Face)",
);
cleaned_output = cleaned_output.replace(
"Axis3d | Edge",
"[Axis3d](/docs/kcl/types/Axis3d) | [Edge](/docs/kcl/types/Edge)",
);
cleaned_output = cleaned_output.replace(
"Axis2d | Edge",
"[Axis2d](/docs/kcl/types/Axis2d) | [Edge](/docs/kcl/types/Edge)",
);
// Fix the links to the types.
// Gross hack for the stupid alias types.
cleaned_output = cleaned_output.replace("TagNode", "TagDeclarator");
let link = "[TagDeclarator](/docs/kcl/types#tag-declaration)";
cleaned_output = cleaned_output.replace("TagDeclarator", link);
let link = "[TagIdentifier](/docs/kcl/types#tag-identifier)";
cleaned_output = cleaned_output.replace("TagIdentifier", link);
cleaned_output
}
fn add_to_types(

View File

@ -1,4 +1,8 @@
use std::{collections::HashSet, fmt, str::FromStr};
use std::{
collections::{HashMap, HashSet},
fmt,
str::FromStr,
};
use regex::Regex;
use tower_lsp::lsp_types::{
@ -9,7 +13,7 @@ use tower_lsp::lsp_types::{
use crate::{
execution::annotations,
parsing::{
ast::types::{Annotation, ImportSelector, Node, PrimitiveType, Type, VariableKind},
ast::types::{Annotation, ImportSelector, ItemVisibility, Node, PrimitiveType, Type, VariableKind},
token::NumericSuffix,
},
ModuleId,
@ -17,19 +21,41 @@ use crate::{
pub fn walk_prelude() -> Vec<DocData> {
let mut visitor = CollectionVisitor::default();
visitor.visit_module("prelude", "").unwrap();
visitor.result
visitor.visit_module("prelude", "", WalkForNames::All).unwrap();
visitor.result.into_values().collect()
}
#[derive(Debug, Clone, Default)]
struct CollectionVisitor {
name: String,
result: Vec<DocData>,
result: HashMap<String, DocData>,
id: usize,
}
#[derive(Clone, Debug)]
enum WalkForNames<'a> {
All,
Selected(Vec<&'a str>),
}
impl<'a> WalkForNames<'a> {
fn contains(&self, name: &str) -> bool {
match self {
WalkForNames::All => true,
WalkForNames::Selected(names) => names.contains(&name),
}
}
fn intersect(&self, names: impl Iterator<Item = &'a str>) -> Self {
match self {
WalkForNames::All => WalkForNames::Selected(names.collect()),
WalkForNames::Selected(mine) => WalkForNames::Selected(names.filter(|n| mine.contains(n)).collect()),
}
}
}
impl CollectionVisitor {
fn visit_module(&mut self, name: &str, preferred_prefix: &str) -> Result<(), String> {
fn visit_module(&mut self, name: &str, preferred_prefix: &str, names: WalkForNames) -> 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))
@ -38,23 +64,29 @@ impl CollectionVisitor {
self.id += 1;
for n in &parsed.body {
if n.visibility() != ItemVisibility::Export {
continue;
}
match n {
crate::parsing::ast::types::BodyItem::ImportStatement(import) if !import.visibility.is_default() => {
match &import.path {
crate::parsing::ast::types::ImportPath::Std { path } => {
match import.selector {
ImportSelector::Glob(..) => self.visit_module(&path[1], "")?,
ImportSelector::None { .. } => {
self.visit_module(&path[1], &format!("{}::", import.module_name().unwrap()))?
}
// Only supports glob or whole-module imports for now (make sure the module is re-exported as well as some of the names in it).
_ => {}
crate::parsing::ast::types::BodyItem::ImportStatement(import) => match &import.path {
crate::parsing::ast::types::ImportPath::Std { path } => match &import.selector {
ImportSelector::Glob(..) => self.visit_module(&path[1], "", names.clone())?,
ImportSelector::None { .. } => {
let name = import.module_name().unwrap();
if names.contains(&name) {
self.visit_module(&path[1], &format!("{}::", name), WalkForNames::All)?;
}
}
p => return Err(format!("Unexpected import: `{p}`")),
ImportSelector::List { items } => {
self.visit_module(&path[1], "", names.intersect(items.iter().map(|n| &*n.name.name)))?
}
},
p => return Err(format!("Unexpected import: `{p}`")),
},
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) => {
if !names.contains(var.name()) {
continue;
}
}
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) if !var.visibility.is_default() => {
let qual_name = if self.name == "prelude" {
"std::".to_owned()
} else {
@ -66,6 +98,10 @@ impl CollectionVisitor {
DocData::Const(ConstData::from_ast(var, qual_name, preferred_prefix, name))
}
};
let key = format!("I:{}", dd.qual_name());
if self.result.contains_key(&key) {
continue;
}
dd.with_meta(&var.outer_attrs);
for a in &var.outer_attrs {
@ -73,15 +109,22 @@ impl CollectionVisitor {
}
dd.with_comments(n.get_comments());
self.result.push(dd);
self.result.insert(key, dd);
}
crate::parsing::ast::types::BodyItem::TypeDeclaration(ty) if !ty.visibility.is_default() => {
crate::parsing::ast::types::BodyItem::TypeDeclaration(ty) => {
if !names.contains(ty.name()) {
continue;
}
let qual_name = if self.name == "prelude" {
"std::".to_owned()
} else {
format!("std::{}::", self.name)
};
let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name, preferred_prefix, name));
let key = format!("T:{}", dd.qual_name());
if self.result.contains_key(&key) {
continue;
}
dd.with_meta(&ty.outer_attrs);
for a in &ty.outer_attrs {
@ -89,7 +132,7 @@ impl CollectionVisitor {
}
dd.with_comments(n.get_comments());
self.result.push(dd);
self.result.insert(key, dd);
}
_ => {}
}
@ -116,6 +159,14 @@ impl DocData {
}
}
pub fn qual_name(&self) -> &str {
match self {
DocData::Fn(f) => &f.qual_name,
DocData::Const(c) => &c.qual_name,
DocData::Ty(t) => &t.qual_name,
}
}
/// The name of the module in which the item is declared, e.g., `sketch`
#[allow(dead_code)]
pub fn module_name(&self) -> &str {

View File

@ -12,7 +12,7 @@ layout: manual
{{{description}}}
```js
```kcl
{{{name}}}{{#if type_}}: {{{type_}}}{{/if}}{{#if value}} = {{{value}}}{{/if}}
```
@ -20,7 +20,7 @@ layout: manual
### Examples
{{#each examples}}
```js
```kcl
{{{this.content}}}
```

View File

@ -12,7 +12,7 @@ layout: manual
{{{description}}}
```js
```kcl
{{{fn_signature}}}
```
@ -47,7 +47,7 @@ layout: manual
{{#each examples}}
{{#if this.content}}
```js
```kcl
{{{this.content}}}
```
{{/if}}

View File

@ -24,7 +24,7 @@ layout: manual
{{#each examples}}
{{#if this.content}}
```js
```kcl
{{{this.content}}}
```
{{/if}}