Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -388,17 +388,15 @@ DOC_KIT ?= tools/doc/node_modules/@nodejs/doc-kit/bin/cli.mjs

node_use_openssl_and_icu = $(call available-node,"-p" \
"process.versions.openssl != undefined && process.versions.icu != undefined")
test/addons/.docbuildstamp: $(DOCBUILDSTAMP_PREREQS) tools/doc/node_modules
test/addons/.docbuildstamp: $(DOCBUILDSTAMP_PREREQS) tools/doc/addon-verify.mjs
@if [ "$(shell $(node_use_openssl_and_icu))" != "true" ]; then \
echo "Skipping .docbuildstamp (no crypto and/or no ICU)"; \
else \
$(RM) -r test/addons/??_*/; \
$(call available-node, \
$(DOC_KIT) generate \
-t addon-verify \
-i doc/api/addons.md \
-o test/addons/ \
--type-map doc/type-map.json \
tools/doc/addon-verify.mjs \
--input doc/api/addons.md \
--output test/addons/ \
) \
[ $$? -eq 0 ] && touch $@; \
fi
Expand Down
66 changes: 66 additions & 0 deletions doc/api/addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ such as any libuv handles registered by the addon.

The following `addon.cc` uses `AddEnvironmentCleanupHook`:


<!-- addon-verify-file worker_support/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -328,6 +331,9 @@ NODE_MODULE_INIT(/* exports, module, context */) {

Test in JavaScript by running:


<!-- addon-verify-file worker_support/test.js -->

```js
// test.js
require('./build/Release/addon');
Expand Down Expand Up @@ -526,6 +532,9 @@ code.
The following example illustrates how to read function arguments passed from
JavaScript and how to return a result:


<!-- addon-verify-file function_arguments/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -585,6 +594,9 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

Once compiled, the example addon can be required and used from within Node.js:


<!-- addon-verify-file function_arguments/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand All @@ -598,6 +610,9 @@ It is common practice within addons to pass JavaScript functions to a C++
function and execute them from there. The following example illustrates how
to invoke such callbacks:


<!-- addon-verify-file callbacks/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -641,6 +656,9 @@ property of `exports`.

To test it, run the following JavaScript:


<!-- addon-verify-file callbacks/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand All @@ -659,6 +677,9 @@ Addons can create and return new objects from within a C++ function as
illustrated in the following example. An object is created and returned with a
property `msg` that echoes the string passed to `createObject()`:


<!-- addon-verify-file object_factory/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -698,6 +719,9 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

To test it in JavaScript:


<!-- addon-verify-file object_factory/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand All @@ -713,6 +737,9 @@ console.log(obj1.msg, obj2.msg);
Another common scenario is creating JavaScript functions that wrap C++
functions and returning those back to JavaScript:


<!-- addon-verify-file function_factory/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -760,6 +787,9 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

To test:


<!-- addon-verify-file function_factory/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand All @@ -774,6 +804,9 @@ console.log(fn());
It is also possible to wrap C++ objects/classes in a way that allows new
instances to be created using the JavaScript `new` operator:


<!-- addon-verify-file wrapping_c_objects/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand All @@ -795,6 +828,9 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)

Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`:


<!-- addon-verify-file wrapping_c_objects/myobject.h -->

```cpp
// myobject.h
#ifndef MYOBJECT_H
Expand Down Expand Up @@ -828,6 +864,9 @@ In `myobject.cc`, implement the various methods that are to be exposed.
In the following code, the method `plusOne()` is exposed by adding it to the
constructor's prototype:


<!-- addon-verify-file wrapping_c_objects/myobject.cc -->

```cpp
// myobject.cc
#include "myobject.h"
Expand Down Expand Up @@ -931,6 +970,9 @@ To build this example, the `myobject.cc` file must be added to the

Test it with:


<!-- addon-verify-file wrapping_c_objects/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand Down Expand Up @@ -968,6 +1010,9 @@ const obj = addon.createObject();

First, the `createObject()` method is implemented in `addon.cc`:


<!-- addon-verify-file factory_of_wrapped_objects/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -1001,6 +1046,9 @@ In `myobject.h`, the static method `NewInstance()` is added to handle
instantiating the object. This method takes the place of using `new` in
JavaScript:


<!-- addon-verify-file factory_of_wrapped_objects/myobject.h -->

```cpp
// myobject.h
#ifndef MYOBJECT_H
Expand Down Expand Up @@ -1033,6 +1081,9 @@ class MyObject : public node::ObjectWrap {

The implementation in `myobject.cc` is similar to the previous example:


<!-- addon-verify-file factory_of_wrapped_objects/myobject.cc -->

```cpp
// myobject.cc
#include <node.h>
Expand Down Expand Up @@ -1147,6 +1198,9 @@ Once again, to build this example, the `myobject.cc` file must be added to the

Test it with:


<!-- addon-verify-file factory_of_wrapped_objects/test.js -->

```js
// test.js
const createObject = require('./build/Release/addon');
Expand Down Expand Up @@ -1175,6 +1229,9 @@ wrapped objects around by unwrapping them with the Node.js helper function
`node::ObjectWrap::Unwrap`. The following examples shows a function `add()`
that can take two `MyObject` objects as input arguments:


<!-- addon-verify-file passing_wrapped_objects_around/addon.cc -->

```cpp
// addon.cc
#include <node.h>
Expand Down Expand Up @@ -1224,6 +1281,9 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)
In `myobject.h`, a new public method is added to allow access to private values
after unwrapping the object.


<!-- addon-verify-file passing_wrapped_objects_around/myobject.h -->

```cpp
// myobject.h
#ifndef MYOBJECT_H
Expand Down Expand Up @@ -1256,6 +1316,9 @@ class MyObject : public node::ObjectWrap {

The implementation of `myobject.cc` remains similar to the previous version:


<!-- addon-verify-file passing_wrapped_objects_around/myobject.cc -->

```cpp
// myobject.cc
#include <node.h>
Expand Down Expand Up @@ -1340,6 +1403,9 @@ void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {

Test it with:


<!-- addon-verify-file passing_wrapped_objects_around/test.js -->

```js
// test.js
const addon = require('./build/Release/addon');
Expand Down
102 changes: 102 additions & 0 deletions tools/doc/addon-verify.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env node

// Extracts C++ addon examples from doc/api/addons.md into numbered test
// directories under test/addons/.
//
// Each code block to extract is preceded by a marker in the markdown:
//
// <!-- addon-verify-file worker_support/addon.cc -->
// ```cpp
// #include <node.h>
// ...
// ```
//
// This produces test/addons/01_worker_support/addon.cc.
// Sections are numbered in order of first appearance.

import { mkdirSync, writeFileSync } from 'node:fs';
import { open } from 'node:fs/promises';
import { join } from 'node:path';
import { parseArgs } from 'node:util';

const { values } = parseArgs({
options: {
input: { type: 'string' },
output: { type: 'string' },
},
});

if (!values.input || !values.output) {
console.error('Usage: addon-verify.mjs --input <file> --output <dir>');
process.exit(1);
}

const src = await open(values.input, 'r');

const MARKER_RE = /^<!--\s*addon-verify-file\s+(\S+?)\/(\S+)\s*-->$/;

const entries = [];
let nextBlockIsAddonVerifyFile = false;
let expectedClosingFenceMarker;
for await (const line of src.readLines()) {
if (expectedClosingFenceMarker) {
// We're inside a Addon snippet
if (line === expectedClosingFenceMarker) {
// End of the snippet
expectedClosingFenceMarker = null;
continue;
}

entries.at(-1).content += `${line}\n`;
}
if (nextBlockIsAddonVerifyFile) {
if (line) {
expectedClosingFenceMarker = line.replace(/\w/g, '');
nextBlockIsAddonVerifyFile = false;
}
continue;
}
const match = MARKER_RE.exec(line);
if (match) {
nextBlockIsAddonVerifyFile = true;
const [, dir, name] = match;
entries.push({ dir, name, content: '' });
}
}

// Collect files grouped by section directory name.
const sections = Map.groupBy(entries, (e) => e.dir);

let idx = 0;
for (const [name, files] of sections) {
const dirName = `${String(++idx).padStart(2, '0')}_${name}`;
const dir = join(values.output, dirName);
mkdirSync(dir, { recursive: true });

for (const file of files) {
let content = file.content;
if (file.name === 'test.js') {
content =
"'use strict';\n" +
"const common = require('../../common');\n" +
content.replace(
"'./build/Release/addon'",
// eslint-disable-next-line no-template-curly-in-string
'`./build/${common.buildType}/addon`',
);
}
writeFileSync(join(dir, file.name), content);
}

// Generate binding.gyp
const names = files.map((f) => f.name);
writeFileSync(join(dir, 'binding.gyp'), JSON.stringify({
targets: [{
target_name: 'addon',
sources: names,
includes: ['../common.gypi'],
}],
}));

console.log(`Generated ${dirName} with files: ${names.join(', ')}`);
}
2 changes: 1 addition & 1 deletion vcbuild.bat
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ for /d %%F in (test\addons\??_*) do (
rd /s /q %%F
)
:: generate
%doc_kit_exe% generate -t addon-verify -i "file://%~dp0doc\api\addons.md" -o "file://%~dp0test\addons" --type-map "file://%~dp0doc\type-map.json"
"%node_exe%" tools\doc\addon-verify.mjs --input "%~dp0doc\api\addons.md" --output "%~dp0test\addons"
if %errorlevel% neq 0 exit /b %errorlevel%
:: building addons
setlocal
Expand Down
Loading