@tensorflow/tfjs-core
Version:
Hardware-accelerated JavaScript library for machine intelligence
181 lines (157 loc) • 5.61 kB
text/typescript
/**
* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
process.on('unhandledRejection', ex => {
throw ex;
});
// Used for logging the number of snippets that have been found.
let snippetCount = 0;
// Used for counting the number of errors that have been found.
let errorCount = 0;
/**
* Parse and evaluate snippets for the src/index.ts from where this script is
* run.
* @param tf The TensorFlow.js module to use when evaluating snippets. If used
* outside core, this should be a union of core and the separate package.
* This is unused here but is used in eval() of the snippets.
*/
// tslint:disable-next-line:no-any
export async function parseAndEvaluateSnippets(tf: any) {
const index = path.join(process.cwd(), 'src/index.ts');
const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
// Use the same compiler options that we use to compile the library
// here.
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
delete tsconfig.compilerOptions.moduleResolution;
const program = ts.createProgram([index], tsconfig.compilerOptions);
const checker = program.getTypeChecker();
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
const children = sourceFile.getChildren();
for (let i = 0; i < children.length; i++) {
await visit(tf, checker, children[i], sourceFile);
}
}
}
if (errorCount === 0) {
console.log(`Parsed and evaluated ${snippetCount} snippets successfully.`);
} else {
console.log(
`Evaluated ${snippetCount} snippets with ${errorCount} errors.`);
process.exit(1);
}
}
async function visit(
// tslint:disable-next-line:no-any
tf: any, checker: ts.TypeChecker, node: ts.Node,
sourceFile: ts.SourceFile) {
const children = node.getChildren();
for (let i = 0; i < children.length; i++) {
await visit(tf, checker, children[i], sourceFile);
}
if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) ||
ts.isMethodDeclaration(node) || ts.isInterfaceDeclaration(node)) {
const symbol = checker.getSymbolAtLocation(node.name);
const jsdoc = getJSDocTag(symbol);
if (jsdoc == null) {
return;
}
// Ignore snippets of methods that have been marked with ignoreCI.
if (jsdoc['ignoreCI']) {
return;
}
const documentation = symbol.getDocumentationComment(checker);
if (documentation == null) {
return;
}
for (let i = 0; i < documentation.length; i++) {
const doc = documentation[i];
const re = /```js.*?```/gs;
const matches = re.exec(doc.text);
if (matches == null) {
return;
}
for (let k = 0; k < matches.length; k++) {
snippetCount++;
const match = matches[k];
const lines = match.split('\n');
const evalLines: string[] = [];
for (let j = 0; j < lines.length; j++) {
let line = lines[j];
if (line.startsWith('```js')) {
line = line.substring('```js'.length);
}
if (line.endsWith('```')) {
line = line.substring(0, line.length - '```'.length);
}
line = line.trim();
if (line.startsWith('*')) {
line = line.substring(1).trim();
}
evalLines.push(line);
}
const srcCode = evalLines.join('\n');
const evalString = '(async function runner() { try { ' + srcCode +
'} catch (e) { reportError(e); } })()';
const oldLog = console.log;
const oldWarn = console.warn;
const reportError = (e: string|Error) => {
oldLog();
oldLog(`Error executing snippet for ${symbol.name} at ${
sourceFile.fileName}`);
oldLog();
oldLog(`\`\`\`js${srcCode}\`\`\``);
oldLog();
console.error(e);
errorCount++;
};
// Overrwrite console.log so we don't spam the console.
console.log = (msg: string) => {};
console.warn = (msg: string) => {};
try {
await eval(evalString);
} catch (e) {
reportError(e);
}
console.log = oldLog;
console.warn = oldWarn;
}
}
}
}
interface JSDoc {
namespace?: string;
ignoreCI?: boolean;
}
function getJSDocTag(symbol: ts.Symbol): JSDoc {
const tags = symbol.getJsDocTags();
for (let i = 0; i < tags.length; i++) {
const jsdocTag = tags[i];
if (jsdocTag.name === 'doc' && jsdocTag.text != null) {
const json = convertDocStringToDocInfoObject(jsdocTag.text.trim());
return json;
}
}
return null;
}
function convertDocStringToDocInfoObject(docString: string): JSDoc {
const jsonString =
docString.replace(/([a-zA-Z0-9]+):/g, '"$1":').replace(/\'/g, '"');
return JSON.parse(jsonString);
}