blob: 68e7a974f7c60f18bf387b05272f00dc833dc23a [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{bail, Context, Error};
use argh::FromArgs;
use log::{error, info, LevelFilter};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;
use rayon::prelude::*;
use serde_json::{json, Value};
use libdoc::DocCompiler;
mod fidljson;
use fidljson::{FidlJson, FidlJsonPackageData, TableOfContentsItem};
use simplelog::{Config, SimpleLogger};
mod templates;
use templates::html::HtmlTemplate;
use templates::markdown::MarkdownTemplate;
use templates::FidldocTemplate;
static FIDLDOC_VERSION: &str = "0.0.4";
static SUPPORTED_FIDLJSON: &str = "0.0.1";
static FIDLDOC_CONFIG_PATH: &str = "fidldoc.config.json";
#[derive(Debug)]
enum TemplateType {
HTML,
Markdown,
}
fn parse_template_type_str(value: &str) -> Result<TemplateType, String> {
match &value.to_lowercase()[..] {
"html" => Ok(TemplateType::HTML),
"markdown" => Ok(TemplateType::Markdown),
_ => Err("invalid template type".to_string()),
}
}
#[derive(Debug, FromArgs)]
/// FIDL documentation generator.
struct Opt {
#[argh(option, short = 'c')]
/// path to a configuration file to provide additional options
config: Option<PathBuf>,
#[argh(option, default = "\"master\".to_string()")]
/// current commit hash, useful to coordinate doc generation with a specific source code revision
tag: String,
#[argh(positional)]
/// set the input file(s) to use
input: Vec<PathBuf>,
#[argh(option, short = 'o', default = "\"/tmp/fidldoc/\".to_string()")]
/// set the output folder
out: String,
#[argh(option, short = 'p', default = "\"/\".to_string()")]
/// set the base URL path for the generated docs
path: String,
#[argh(
option,
short = 't',
from_str_fn(parse_template_type_str),
default = "TemplateType::Markdown"
)]
/// select the template to use to render the docs
template: TemplateType,
#[argh(switch, short = 'v')]
/// generate verbose output
verbose: bool,
#[argh(switch)]
/// do not generate any output
silent: bool,
#[argh(switch)]
/// experimental documentation checks (do not generate any output)
/// TODO(fxbug.dev/71688) Remove the flag when the checks will be fully developed.
experimental_checks: bool,
}
fn main() {
let opt: Opt = argh::from_env();
if let Err(e) = run(opt) {
error!("Error: {}", e);
process::exit(1);
}
}
fn run(opt: Opt) -> Result<(), Error> {
let mut input_files = opt.input;
normalize_input_files(&mut input_files);
let output = &opt.out;
let output_path = PathBuf::from(output);
let url_path = &opt.path;
let template_type = &opt.template;
let template = select_template(template_type, &output_path)
.with_context(|| format!("Unable to instantiate template {:?}", template_type))?;
if opt.silent && opt.verbose {
bail!("cannot use --silent and --verbose together");
}
if opt.verbose {
SimpleLogger::init(LevelFilter::Info, Config::default())?;
} else {
SimpleLogger::init(LevelFilter::Error, Config::default())?;
}
// Read in fidldoc.config.json
let fidl_config_file = match opt.config {
Some(filepath) => filepath,
None => get_fidldoc_config_default_path()
.with_context(|| format!("Unable to retrieve default config file location"))?,
};
info!("Using config file from {}", fidl_config_file.display());
let fidl_config = read_fidldoc_config(&fidl_config_file)
.with_context(|| format!("Error parsing {}", &fidl_config_file.display()))?;
create_output_dir(&output_path)
.with_context(|| format!("Unable to create output directory {}", output_path.display()))?;
// Parse input files to get declarations, package set and fidl json map
let FidlJsonPackageData { declarations, fidl_json_map } =
process_fidl_json_files(input_files.to_vec());
if opt.experimental_checks {
// Only checks the documentation (no generation).
fidl_json_map
.par_iter()
.try_for_each(|(_package, package_fidl_json)| check_documentation(package_fidl_json))
.expect("Errors");
if !opt.silent {
println!("documentation checked");
}
} else {
// The table of contents lists all packages in alphabetical order.
let table_of_contents = create_toc(&fidl_json_map);
// Modifications to the fidldoc object
let main_fidl_doc = json!({
"table_of_contents": table_of_contents,
"fidldoc_version": FIDLDOC_VERSION,
"config": fidl_config,
"search": declarations,
"url_path": url_path,
});
// Create main page
template.render_main_page(&main_fidl_doc).expect("Unable to render main page");
let tag = &opt.tag;
let output_path_string = &output_path.display();
fidl_json_map
.par_iter()
.try_for_each(|(package, package_fidl_json)| {
render_fidl_interface(
package,
package_fidl_json,
&table_of_contents,
&fidl_config,
&tag,
&declarations,
&url_path,
&template_type,
&output_path,
)
})
.expect("Unable to write FIDL reference files");
if !opt.silent {
println!("Generated documentation at {}", &output_path_string);
}
}
Ok(())
}
/// Checks all the documentation from the FIDL files.
///
/// FIDL documentation always starts with 3 slashes and a space. That means that, for a declaration
/// which starts at column 1, the documentation starts 4 characters later at column 5.
///
/// Declarations which are indented 4 characters start at column 5. That means that, for these
/// declarations, the documentation starts at column 9.
///
/// The indentation is enforced by the FIDL linter/formatter.
fn check_documentation(package_fidl_json: &FidlJson) -> Result<(), String> {
let mut compiler = DocCompiler::new();
for bits_declaration in package_fidl_json.bits_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&bits_declaration["maybe_attributes"],
&bits_declaration["location"],
/*column=*/ 5,
);
for members in bits_declaration["members"].as_array().iter() {
for member in members.iter() {
check_declaration_documentation(
&mut compiler,
&member["maybe_attributes"],
&member["location"],
/*column=*/ 9,
);
}
}
}
for const_declaration in package_fidl_json.const_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&const_declaration["maybe_attributes"],
&const_declaration["location"],
/*column=*/ 5,
);
}
for enum_declaration in package_fidl_json.enum_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&enum_declaration["maybe_attributes"],
&enum_declaration["location"],
/*column=*/ 5,
);
for members in enum_declaration["members"].as_array().iter() {
for member in members.iter() {
check_declaration_documentation(
&mut compiler,
&member["maybe_attributes"],
&member["location"],
/*column=*/ 9,
);
}
}
}
for interface_declaration in package_fidl_json.interface_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&interface_declaration["maybe_attributes"],
&interface_declaration["location"],
/*column=*/ 5,
);
for methods in interface_declaration["methods"].as_array().iter() {
for method in methods.iter() {
check_declaration_documentation(
&mut compiler,
&method["maybe_attributes"],
&method["location"],
/*column=*/ 9,
);
}
}
}
for struct_declaration in package_fidl_json.struct_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&struct_declaration["maybe_attributes"],
&struct_declaration["location"],
/*column=*/ 5,
);
for members in struct_declaration["members"].as_array().iter() {
for member in members.iter() {
check_declaration_documentation(
&mut compiler,
&member["maybe_attributes"],
&member["location"],
/*column=*/ 9,
);
}
}
}
for table_declaration in package_fidl_json.table_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&table_declaration["maybe_attributes"],
&table_declaration["location"],
/*column=*/ 5,
);
for members in table_declaration["members"].as_array().iter() {
for member in members.iter() {
check_declaration_documentation(
&mut compiler,
&member["maybe_attributes"],
&member["location"],
/*column=*/ 9,
);
}
}
}
for union_declaration in package_fidl_json.union_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&union_declaration["maybe_attributes"],
&union_declaration["location"],
/*column=*/ 5,
);
for members in union_declaration["members"].as_array().iter() {
for member in members.iter() {
check_declaration_documentation(
&mut compiler,
&member["maybe_attributes"],
&member["location"],
/*column=*/ 9,
);
}
}
}
for type_alias_declaration in package_fidl_json.type_alias_declarations.iter() {
check_declaration_documentation(
&mut compiler,
&type_alias_declaration["maybe_attributes"],
&type_alias_declaration["location"],
/*column=*/ 5,
);
}
if !compiler.errors.is_empty() {
// All the documentation has been parsed for a JSON file.
// We encounered errors.
// Prints the errors discovered.
print!("{}", compiler.errors);
Err("Documentation has errors".to_string())
} else {
Ok(())
}
}
/// Checks some documentation associated to a declaration.
fn check_declaration_documentation(
compiler: &mut DocCompiler,
attributes_value: &Value,
location: &Value,
column: u32,
) {
if let serde_json::Value::Object(location) = location {
for attributes in attributes_value.as_array().iter() {
for attribute in attributes.iter() {
if attribute["name"] == "Doc" {
if let serde_json::Value::String(text) = &attribute["value"] {
compiler.parse_doc(
location["filename"].to_string(),
infer_doc_line(
location["line"].to_string().parse::<u32>().unwrap_or(0),
&text,
),
column,
clean_doc(&text),
);
}
}
}
}
}
}
/// Infers the line number for the first line of documentation.
///
/// This method assumes that the documentation is right before the declaration.
/// The column is hard coded because, for a kind of declaration, the column is always the same.
fn infer_doc_line(line: u32, text: &String) -> u32 {
line - (text.matches("\n").count() as u32)
}
/// Cleans the documentation.
///
/// This method removes the first character at the beginning of the first line (it should be a
/// space) and the first space at the beginning of all the other lines.
fn clean_doc(text: &String) -> String {
if text.len() == 0 {
"".to_owned()
} else {
// First removes the first character at the beginning of the text (the first line). Then
// removes all the spaces at the beginning of the other lines using replace.
text[1..].replace("\n ", "\n")
}
}
fn render_fidl_interface(
package: &String,
package_fidl_json: &FidlJson,
table_of_contents: &Vec<TableOfContentsItem>,
fidl_config: &Value,
tag: &String,
declarations: &Vec<String>,
url_path: &String,
template_type: &TemplateType,
output_path: &PathBuf,
) -> Result<(), Error> {
// Modifications to the fidldoc object
let fidl_doc = json!({
"version": package_fidl_json.version,
"name": package_fidl_json.name,
"maybe_attributes": package_fidl_json.maybe_attributes,
"library_dependencies": package_fidl_json.library_dependencies,
"bits_declarations": package_fidl_json.bits_declarations,
"const_declarations": package_fidl_json.const_declarations,
"enum_declarations": package_fidl_json.enum_declarations,
"interface_declarations": package_fidl_json.interface_declarations,
"table_declarations": package_fidl_json.table_declarations,
"struct_declarations": package_fidl_json.struct_declarations,
"type_alias_declarations": package_fidl_json.type_alias_declarations,
"union_declarations": package_fidl_json.union_declarations,
"declaration_order": package_fidl_json.declaration_order,
"declarations": package_fidl_json.declarations,
"table_of_contents": table_of_contents,
"fidldoc_version": FIDLDOC_VERSION,
"config": fidl_config,
"tag": tag,
"search": declarations,
"url_path": url_path,
});
let template = select_template(&template_type, &output_path)
.with_context(|| format!("Unable to instantiate template {:?}", template_type));
match template?.render_interface(&package, &fidl_doc) {
Err(why) => error!("Unable to render interface {}: {:?}", &package, why),
Ok(()) => info!("Generated interface documentation for {}", &package),
}
Ok(())
}
fn select_template<'a>(
template_type: &TemplateType,
output_path: &'a PathBuf,
) -> Result<Box<dyn FidldocTemplate + 'a>, Error> {
// Instantiate the template selected by the user
let template: Box<dyn FidldocTemplate> = match template_type {
TemplateType::HTML => {
let template = HtmlTemplate::new(&output_path)?;
Box::new(template)
}
TemplateType::Markdown => {
let template = MarkdownTemplate::new(&output_path);
Box::new(template)
}
};
Ok(template)
}
fn get_fidldoc_config_default_path() -> Result<PathBuf, Error> {
// If the fidldoc config file is not available, it should be found
// in the same directory as the executable.
// This needs to be calculated at runtime.
let fidldoc_executable = std::env::current_exe()?;
let fidldoc_execution_directory = fidldoc_executable.parent().unwrap();
let fidl_config_default_path = fidldoc_execution_directory.join(FIDLDOC_CONFIG_PATH);
Ok(fidl_config_default_path)
}
fn read_fidldoc_config(config_path: &Path) -> Result<Value, Error> {
let fidl_config_str = fs::read_to_string(config_path)
.with_context(|| format!("Couldn't open file {}", config_path.display()))?;
Ok(serde_json::from_str(&fidl_config_str)?)
}
fn should_process_fidl_json(fidl_json: &FidlJson) -> bool {
if fidl_json.version != SUPPORTED_FIDLJSON {
error!(
"Error parsing {}: fidldoc does not support version {}, only {}",
fidl_json.name, fidl_json.version, SUPPORTED_FIDLJSON
);
return false;
}
if fidl_json.maybe_attributes.iter().any(|attr| attr["name"] == "NoDoc") {
info!("Skipping library with NoDoc attribute: {}", fidl_json.name);
return false;
}
true
}
fn process_fidl_json_files(input_files: Vec<PathBuf>) -> FidlJsonPackageData {
let mut package_data = FidlJsonPackageData::new();
for file in input_files {
let fidl_file_path = PathBuf::from(&file);
let fidl_json = match FidlJson::from_path(&fidl_file_path) {
Err(why) => {
error!("Error parsing {}: {}", file.display(), why);
continue;
}
Ok(json) => json,
};
if should_process_fidl_json(&fidl_json) {
package_data.insert(fidl_json);
}
}
// Sort declarations inside each package
package_data.fidl_json_map.par_iter_mut().for_each(|(_, package_fidl_json)| {
package_fidl_json.sort_declarations();
});
package_data
}
fn create_toc(fidl_json_map: &HashMap<String, FidlJson>) -> Vec<TableOfContentsItem> {
// The table of contents lists all packages in alphabetical order.
let mut table_of_contents: Vec<_> = fidl_json_map
.par_iter()
.map(|(package_name, fidl_json)| TableOfContentsItem {
name: package_name.clone(),
link: format!("{name}/index", name = package_name),
description: get_library_description(&fidl_json.maybe_attributes),
})
.collect();
table_of_contents.sort_unstable_by(|a, b| a.name.cmp(&b.name));
table_of_contents
}
fn get_library_description(maybe_attributes: &Vec<Value>) -> String {
for attribute in maybe_attributes {
if attribute["name"] == "Doc" {
return attribute["value"]
.as_str()
.expect("Unable to retrieve string value for library description")
.to_string();
}
}
"".to_string()
}
fn create_output_dir(path: &PathBuf) -> Result<(), Error> {
if path.exists() {
info!("Directory {} already exists", path.display());
// Clear out the output folder
fs::remove_dir_all(path)
.with_context(|| format!("Unable to remove output directory {}", path.display()))?;
info!("Removed directory {}", path.display());
}
// Re-create output folder
fs::create_dir_all(path)
.with_context(|| format!("Unable to create output directory {}", path.display()))?;
info!("Created directory {}", path.display());
Ok(())
}
// Pre-processes the list of input files by removing duplicates.
fn normalize_input_files(input: &mut Vec<PathBuf>) {
input.sort_unstable();
input.dedup();
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::{tempdir, NamedTempFile};
use std::path::PathBuf;
use serde_json::Map;
#[test]
fn select_template_test() {
let path = PathBuf::new();
let html_template = select_template(&TemplateType::HTML, &path).unwrap();
assert_eq!(html_template.name(), "HTML".to_string());
let markdown_template = select_template(&TemplateType::Markdown, &path).unwrap();
assert_eq!(markdown_template.name(), "Markdown".to_string());
}
#[test]
fn create_toc_test() {
let mut fidl_json_map: HashMap<String, FidlJson> = HashMap::new();
fidl_json_map.insert(
"fuchsia.media".to_string(),
FidlJson {
name: "fuchsia.media".to_string(),
version: "0.0.1".to_string(),
maybe_attributes: Vec::new(),
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
},
);
fidl_json_map.insert(
"fuchsia.auth".to_string(),
FidlJson {
name: "fuchsia.auth".to_string(),
version: "0.0.1".to_string(),
maybe_attributes: vec![json!({"name": "Doc", "value": "Fuchsia Auth API"})],
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
},
);
fidl_json_map.insert(
"fuchsia.camera.common".to_string(),
FidlJson {
name: "fuchsia.camera.common".to_string(),
version: "0.0.1".to_string(),
maybe_attributes: vec![json!({"some_key": "key", "some_value": "not_description"})],
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
},
);
let toc = create_toc(&fidl_json_map);
assert_eq!(toc.len(), 3);
let item0 = toc.get(0).unwrap();
assert_eq!(item0.name, "fuchsia.auth".to_string());
assert_eq!(item0.link, "fuchsia.auth/index".to_string());
assert_eq!(item0.description, "Fuchsia Auth API".to_string());
let item1 = toc.get(1).unwrap();
assert_eq!(item1.name, "fuchsia.camera.common".to_string());
assert_eq!(item1.link, "fuchsia.camera.common/index".to_string());
assert_eq!(item1.description, "".to_string());
let item2 = toc.get(2).unwrap();
assert_eq!(item2.name, "fuchsia.media".to_string());
assert_eq!(item2.link, "fuchsia.media/index".to_string());
assert_eq!(item2.description, "".to_string());
}
#[test]
fn get_library_description_test() {
let maybe_attributes = vec![
json!({"name": "Not Doc", "value": "Not the description"}),
json!({"name": "Doc", "value": "Fuchsia Auth API"}),
];
let description = get_library_description(&maybe_attributes);
assert_eq!(description, "Fuchsia Auth API".to_string());
}
#[test]
fn create_output_dir_test() {
// Create a temp dir to run tests on
let dir = tempdir().expect("Unable to create temp dir");
let dir_path = PathBuf::from(dir.path());
// Add a temp file inside the temp dir
let file_path = dir_path.join("temp.txt");
File::create(file_path).expect("Unable to create temp file");
create_output_dir(&dir_path).expect("create_output_dir failed");
assert!(dir_path.exists());
assert!(dir_path.is_dir());
// The temp file has been deleted
assert_eq!(dir_path.read_dir().unwrap().count(), 0);
}
#[test]
fn get_fidldoc_config_default_path_test() {
// Ensure that I get a valid filepath
let default = std::env::current_exe().unwrap().parent().unwrap().join(FIDLDOC_CONFIG_PATH);
assert_eq!(default, get_fidldoc_config_default_path().unwrap());
}
#[test]
fn read_fidldoc_config_test() {
// Generate a test config file
let fidl_config_sample = json!({
"title": "Fuchsia FIDLs"
});
// Write this to a temporary file
let mut fidl_config_file = NamedTempFile::new().unwrap();
fidl_config_file
.write(fidl_config_sample.to_string().as_bytes())
.expect("Unable to write to temporary file");
// Read in file
let fidl_config = read_fidldoc_config(&fidl_config_file.path()).unwrap();
assert_eq!(fidl_config["title"], "Fuchsia FIDLs".to_string());
}
#[test]
fn normalize_input_files_test() {
let mut input_files = vec![
PathBuf::from(r"/tmp/file1"),
PathBuf::from(r"/file2"),
PathBuf::from(r"/usr/file1"),
];
normalize_input_files(&mut input_files);
assert_eq!(input_files.len(), 3);
let mut dup_input_files = vec![
PathBuf::from(r"/tmp/file1"),
PathBuf::from(r"/file2"),
PathBuf::from(r"/tmp/file1"),
];
normalize_input_files(&mut dup_input_files);
assert_eq!(dup_input_files.len(), 2);
}
#[test]
fn should_process_test() {
let fidl_json = FidlJson {
name: "fuchsia.camera.common".to_string(),
version: SUPPORTED_FIDLJSON.to_string(),
maybe_attributes: vec![json!({"name": "not NoDoc", "value": ""})],
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
assert_eq!(should_process_fidl_json(&fidl_json), true);
}
#[test]
fn check_version_test() {
let fidl_json = FidlJson {
name: "fuchsia.camera.common".to_string(),
version: "not a valid version string".to_string(),
maybe_attributes: vec![json!({"name": "not NoDoc", "value": ""})],
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
assert_eq!(should_process_fidl_json(&fidl_json), false);
}
#[test]
fn check_nodoc_attribute_test() {
let fidl_json = FidlJson {
name: "fuchsia.camera.common".to_string(),
version: SUPPORTED_FIDLJSON.to_string(),
maybe_attributes: vec![json!({"name": "NoDoc", "value": ""})],
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: Vec::new(),
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
assert_eq!(should_process_fidl_json(&fidl_json), false);
}
#[test]
fn check_documentation_ok() {
let fidl_json = FidlJson {
name: "fuchsia.camera.common".to_string(),
version: SUPPORTED_FIDLJSON.to_string(),
maybe_attributes: Vec::new(),
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: vec![json!({
"name": "fuchsia.sysmem/HeapType",
"location": {
"filename": "../../sdk/fidl/fuchsia.sysmem/constraints.fidl",
"line": 216,
"column": 6,
"length": 8
},
"maybe_attributes": [
{
"name": "Doc",
"value": "Device specific types should have bit 60 set.\n"
}
]})],
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
check_documentation(&fidl_json).expect("Errors");
}
#[test]
fn check_documentation_nok() {
let fidl_json = FidlJson {
name: "fuchsia.camera.common".to_string(),
version: SUPPORTED_FIDLJSON.to_string(),
maybe_attributes: Vec::new(),
library_dependencies: Vec::new(),
bits_declarations: Vec::new(),
const_declarations: Vec::new(),
enum_declarations: vec![json!({
"name": "fuchsia.sysmem/HeapType",
"location": {
"filename": "../../sdk/fidl/fuchsia.sysmem/constraints.fidl",
"line": 216,
"column": 6,
"length": 8
},
"maybe_attributes": [
{
"name": "Doc",
"value": "Device specific types should have bit '60 set.\n"
}
]})],
interface_declarations: Vec::new(),
table_declarations: Vec::new(),
type_alias_declarations: Vec::new(),
struct_declarations: Vec::new(),
union_declarations: Vec::new(),
declaration_order: Vec::new(),
declarations: Map::new(),
};
let result = check_documentation(&fidl_json);
assert!(result.is_err());
}
}