Files
MapLibreParticleLab/platform/darwin/scripts/update-examples.js
mwilsnd 20d9e1028c Platform flattening overhaul (#1393)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
2023-07-26 17:50:34 +02:00

149 lines
5.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const execFileSync = require('child_process').execFileSync;
const _ = require('lodash');
require('../../../../../scripts/style-code');
const examplesSrc = fs.readFileSync('platform/darwin/test/MLNDocumentationExampleTests.swift', 'utf8');
// Regex extracts the following block
// /** Front matter to describe the example. **/
// func testMLNClass$member() {
// ...
// // #-example-code
// let sampleCode: String?
// // #-end-example-code
// ...
// }
//
// into the following regex groups:
// 1 (test method name): "MLNClass" or "MLNClass$member" or "MLNClass$initWithArg_anotherArg_"
// 2 (indentation): " "
// 3 (sample code): "let sampleCode: String?"
const exampleRegex = /func test([\w$]+)\s*\(\)\s*\{[^]*?\n([ \t]+)\/\/#-example-code\n([^]+?)\n\2\/\/#-end-example-code\n/gm;
/**
* Returns the given source with example code inserted into the documentation
* comment for the symbol at the given one-based line.
*
* @param {String} src Source code to insert the example code into.
* @param {Number} line One-based line number of the symbol being documented.
* @param {String} exampleCode Example code to insert.
* @returns {String} `src` with `exampleCode` inserted just above `line`.
*/
function completeSymbolInSource(src, line, exampleCode) {
// Split the file contents right before the symbol declaration (but after its documentation comment).
let srcUpToSymbol = src.split('\n', line - 1).join('\n');
let srcFromSymbol = src.substr(srcUpToSymbol.length);
// Match the documentation comment block that is not followed by the beginning or end of a declaration.
let commentMatch = srcUpToSymbol.match(/\/\*\*\s*(?:[^*]|\*(?!\/))+?\s*\*\/[^;{}]*?$/);
// Replace the Swift code block with the test method’s contents.
let completedComment = commentMatch[0].replace(/^([ \t]*)```swift\n[^]*?```/m, function (m, indentation) {
// Apply the original indentation to each line.
return ('```swift\n' + exampleCode + '\n```').replace(/^/gm, indentation);
});
// Splice the modified comment into the overall file contents.
srcUpToSymbol = (srcUpToSymbol.substr(0, commentMatch.index) + completedComment +
srcUpToSymbol.substr(commentMatch.index + commentMatch[0].length));
return srcUpToSymbol + srcFromSymbol;
}
let examples = {};
let match;
while ((match = exampleRegex.exec(examplesSrc)) !== null) {
let testMethodName = match[1],
indentation = match[2],
exampleCode = match[3];
// Trim leading whitespace from the example code.
exampleCode = exampleCode.replace(new RegExp('^' + indentation, 'gm'), '');
examples[testMethodName] = exampleCode;
}
function completeExamples(os) {
console.log(`Installing ${os} SDK examples…`);
let sdk = os === 'iOS' ? 'iphonesimulator' : 'macosx';
let sysroot = execFileSync('xcrun', ['--show-sdk-path', '--sdk', sdk]).toString().trim();
let umbrellaPath = `platform/${os.toLowerCase()}/src/Mapbox.h`;
let docArgs = ['doc', '--objc', umbrellaPath, '--',
'-x', 'objective-c', '-I', 'platform/darwin/src/', '-isysroot', sysroot];
let docStr = execFileSync('sourcekitten', docArgs, { maxBuffer: Infinity }).toString().trim();
let docJson = JSON.parse(docStr);
_.forEach(docJson, function (result) {
_.forEach(result, function (structure, path) {
let src;
let newSrc;
// Recursively search for code blocks in documentation comments and populate
// them with example code from the test methods. Find and replace the code
// blocks in reverse to keep the SourceKitten line numbers accurate.
_.forEachRight(structure['key.substructure'], function completeSubstructure(substructure, idx, substructures, symbolPath) {
if (!symbolPath) {
symbolPath = [substructure['key.name']];
}
_.forEachRight(substructure['key.substructure'], function (substructure, idx, substructures) {
completeSubstructure(substructure, idx, substructures, _.concat(symbolPath, substructure['key.name']));
});
let comment = substructure['key.doc.comment'];
if (!comment || !comment.match(/^(?:\s*)```swift\n/m)) {
return;
}
// Lazily read in the existing file.
if (!src) {
newSrc = src = fs.readFileSync(path, 'utf8');
}
// Get the contents of the test method whose name matches the symbol path.
let testMethodName = symbolPath.join('$').replace(/\$[+-]/, '$').replace(/:/g, '_');
let example = examples[testMethodName];
if (!example) {
console.error(`MLNDocumentationExampleTests.test${testMethodName}() not found.`);
process.exit(1);
}
// Resolve conditional compilation blocks.
example = example.replace(/^(\s*)#if\s+os\((iOS|macOS)\)\n([^]*?)(?:^\1#else\n([^]*?))?^\1#endif\b\n?/gm,
function (m, indentation, ifOs, ifCase, elseCase) {
return (os === ifOs ? ifCase : elseCase).replace(new RegExp('^ ', 'gm'), '');
}).replace(/\n$/, '');
// Insert the test method contents into the documentation comment just
// above the substructure.
let startLine = substructure['key.parsed_scope.start'];
newSrc = completeSymbolInSource(newSrc, startLine, example);
});
if (!src) {
return;
}
// Write out the modified file contents.
writeIfModified(path, newSrc)
});
});
}
function ensureSourceKittenIsInstalled() {
try {
execFileSync('which', ['sourcekitten']);
} catch (e) {
console.log(`Installing SourceKitten via Homebrew…`);
execFileSync('brew', ['install', 'sourcekitten']);
}
}
ensureSourceKittenIsInstalled();
// Where a particular comment is part of both SDKs, prefer the iOS example.
completeExamples('macOS');
completeExamples('iOS');