Initial import of the lib to parse javascript code, in the same vein as we parse React proptypes

This commit is contained in:
Christopher Chedeau 2015-03-10 13:55:54 -07:00
parent 3d7b5d263a
commit 83581cfe6b
9 changed files with 2137 additions and 3 deletions

View File

@ -0,0 +1,557 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*global exports:true*/
"use strict";
var Syntax = require('esprima-fb').Syntax;
function toObject(/*array*/ array) /*object*/ {
var object = {};
for (var i = 0; i < array.length; i++) {
var value = array[i];
object[value] = value;
}
return object;
}
function reverseObject(/*object*/ object) /*object*/ {
var reversed = {};
for (var key in object) {
if (object.hasOwnProperty(key)) {
reversed[object[key]] = key
}
}
return reversed;
}
function getTagName(string) {
if (string === 'A') {
return 'Anchor';
}
if (string === 'IMG') {
return 'Image';
}
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
var TOKENS = {
STRING: 'string',
OPENGENERIC: '<',
CLOSEGENERIC: '>',
COMMA: ',',
OPENPAREN: '(',
CLOSEPAREN: ')',
COLON: ':',
BAR: '|',
NULLABLE: '?',
EOL: 'eol',
OPENSEGMENT: '{',
CLOSESEGMENT: '}'
};
var TOKENMAP = reverseObject(TOKENS);
var SYMBOLS = {
SIMPLE: 'simple',
UNION: 'union',
GENERIC: 'generic',
FUNCTION: 'function',
SEGMENT: 'segment'
};
var PARSERS = {
SIMPLE: 1,
UNION: 2,
GENERIC: 4,
FUNCTION: 8,
SEGMENT: 16
};
/*----- tokenizer-----*/
function createTokenStream(source) {
var stream = [], string, pos = 0;
do {
var character = source.charAt(pos);
if (character && /\w/.test(character)) {
string = string ? string + character : character;
} else {
if (string) {
stream.push({ type: TOKENS.STRING, value: string });
string = null;
}
if (character) {
if (character in TOKENMAP) {
stream.push({ type: character });
} else {
throwError('Invalid character: ' + character + ' at pos: ' + pos);
}
} else {
stream.push({ type: TOKENS.EOL });
break;
}
}
} while (++pos);
return stream;
}
/*----- parser-----*/
var SIMPLETYPES = toObject([
'string',
'number',
'regexp',
'boolean',
'object',
'function',
'array',
'date',
'blob',
'file',
'int8array',
'uint8array',
'int16array',
'uint16array',
'int32array',
'uint32array',
'float32array',
'float64array',
'filelist',
'promise',
'map',
'set'
]);
// types typically used in legacy docblock
var BLACKLISTED = toObject([
'Object',
'Boolean',
'bool',
'Number',
'String',
'int',
'Node',
'Element',
]);
function createAst(type, value, length) {
return { type: type, value: value, length: length };
}
function nullable(fn) {
return function(stream, pos) {
var nullable = stream[pos].type == '?' && ++pos;
var ast = fn(stream, pos);
if (ast && nullable) {
ast.nullable = true;
ast.length++;
}
return ast;
};
}
var parseSimpleType = nullable(function(stream, pos) {
if (stream[pos].type == TOKENS.STRING) {
var value = stream[pos].value;
if ((/^[a-z]/.test(value) && !(value in SIMPLETYPES))
|| value in BLACKLISTED) {
throwError('Invalid type ' + value + ' at pos: ' + pos);
}
return createAst(SYMBOLS.SIMPLE, stream[pos].value, 1);
}
});
var parseUnionType = nullable(function(stream, pos) {
var parsers =
PARSERS.SIMPLE | PARSERS.GENERIC | PARSERS.FUNCTION | PARSERS.SEGMENT;
var list = parseList(stream, pos, TOKENS.BAR, parsers);
if (list.value.length > 1) {
return createAst(SYMBOLS.UNION, list.value, list.length);
}
});
var parseGenericType = nullable(function(stream, pos, ast) {
var genericAst, typeAst;
if ((genericAst = parseSimpleType(stream, pos)) &&
stream[pos + genericAst.length].type == TOKENS.OPENGENERIC &&
(typeAst = parseAnyType(stream, pos += genericAst.length + 1))) {
if (stream[pos + typeAst.length].type != TOKENS.CLOSEGENERIC) {
throwError('Missing ' + TOKENS.CLOSEGENERIC +
' at pos: ' + pos + typeAst.length);
}
return createAst(SYMBOLS.GENERIC, [genericAst, typeAst],
genericAst.length + typeAst.length + 2);
}
});
var parseFunctionType = nullable(function(stream, pos) {
if (stream[pos].type == TOKENS.STRING &&
stream[pos].value == 'function' &&
stream[++pos].type == TOKENS.OPENPAREN) {
var list = stream[pos + 1].type != TOKENS.CLOSEPAREN
? parseList(stream, pos + 1, TOKENS.COMMA)
: {value: [], length: 0};
pos += list.length + 1;
if (stream[pos].type == TOKENS.CLOSEPAREN) {
var length = list.length + 3, returnAst;
if (stream[++pos].type == TOKENS.COLON) {
returnAst = parseAnyType(stream, ++pos);
if (!returnAst) {
throwError('Could not parse return type at pos: ' + pos);
}
length += returnAst.length + 1;
}
return createAst(SYMBOLS.FUNCTION, [list.value, returnAst || null],
length);
}
}
});
function parseSegmentType(stream, pos) {
var segmentAst;
if (stream[pos].type == TOKENS.OPENSEGMENT &&
(segmentAst = parseAnyType(stream, ++pos))) {
pos += segmentAst.length
if (stream[pos].type == TOKENS.CLOSESEGMENT) {
return createAst(SYMBOLS.SEGMENT, segmentAst, segmentAst.length + 2);
}
}
}
function parseAnyType(stream, pos, parsers) {
if (!parsers) parsers =
PARSERS.SEGMENT | PARSERS.SIMPLE | PARSERS.UNION | PARSERS.GENERIC
| PARSERS.FUNCTION;
var ast =
(parsers & PARSERS.UNION && parseUnionType(stream, pos)) ||
(parsers & PARSERS.SEGMENT && parseSegmentType(stream, pos)) ||
(parsers & PARSERS.GENERIC && parseGenericType(stream, pos)) ||
(parsers & PARSERS.FUNCTION && parseFunctionType(stream, pos)) ||
(parsers & PARSERS.SIMPLE && parseSimpleType(stream, pos));
if (!ast) {
throwError('Could not parse ' + stream[pos].type);
}
return ast;
}
function parseList(stream, pos, separator, parsers) {
var symbols = [], childAst, length = 0, separators = 0;
while (true) {
if (childAst = parseAnyType(stream, pos, parsers)) {
symbols.push(childAst);
length += childAst.length;
pos += childAst.length;
if (stream[pos].type == separator) {
length++;
pos++;
separators++;
continue;
}
}
break;
}
if (symbols.length && symbols.length != separators + 1) {
throwError('Malformed list expression');
}
return {
value: symbols,
length: length
};
}
var _source;
function throwError(msg) {
throw new Error(msg + '\nSource: ' + _source);
}
function parse(source) {
_source = source;
var stream = createTokenStream(source);
var ast = parseAnyType(stream, 0);
if (ast) {
if (ast.length + 1 != stream.length) {
console.log(ast);
throwError('Could not parse ' + stream[ast.length].type +
' at token pos:' + ast.length);
}
return ast;
} else {
throwError('Failed to parse the source');
}
}
exports.createTokenStream = createTokenStream;
exports.parse = parse;
exports.parseList = parseList;
/*----- compiler -----*/
var compilers = {};
compilers[SYMBOLS.SIMPLE] = function(ast) {
switch (ast.value) {
case 'DOMElement': return 'HTMLElement';
case 'FBID': return 'string';
default: return ast.value;
}
};
compilers[SYMBOLS.UNION] = function(ast) {
return ast.value.map(function(symbol) {
return compile(symbol);
}).join(TOKENS.BAR);
};
compilers[SYMBOLS.GENERIC] = function(ast) {
var type = compile(ast.value[0]);
var parametricType = compile(ast.value[1]);
if (type === 'HTMLElement') {
return 'HTML' + getTagName(parametricType) + 'Element';
}
return type + '<' + parametricType + '>';
};
compilers[SYMBOLS.FUNCTION] = function(ast) {
return 'function(' + ast.value[0].map(function(symbol) {
return compile(symbol);
}).join(TOKENS.COMMA) + ')' +
(ast.value[1] ? ':' + compile(ast.value[1]) : '');
};
function compile(ast) {
return (ast.nullable ? '?' : '') + compilers[ast.type](ast);
}
exports.compile = compile;
/*----- normalizer -----*/
function normalize(ast) {
if (ast.type === SYMBOLS.UNION) {
return ast.value.map(normalize).reduce(function(list, nodes) {
return list ? list.concat(nodes) : nodes;
});
}
var valueNodes = ast.type === SYMBOLS.GENERIC
? normalize(ast.value[1])
: [ast.value];
return valueNodes.map(function(valueNode) {
return createAst(
ast.type,
ast.type === SYMBOLS.GENERIC
? [ast.value[0], valueNode]
: valueNode,
ast.length);
});
}
exports.normalize = function(ast) {
var normalized = normalize(ast);
normalized = normalized.length === 1
? normalized[0]
: createAst(SYMBOLS.UNION, normalized, normalized.length);
if (ast.nullable) {
normalized.nullable = true;
}
return normalized;
};
/*----- Tracking TypeAliases -----*/
function initTypeAliasTracking(state) {
state.g.typeAliasScopes = [];
}
function pushTypeAliases(state, typeAliases) {
state.g.typeAliasScopes.unshift(typeAliases);
}
function popTypeAliases(state) {
state.g.typeAliasScopes.shift();
}
function getTypeAlias(id, state) {
var typeAliasScopes = state.g.typeAliasScopes;
for (var ii = 0; ii < typeAliasScopes.length; ii++) {
var typeAliasAnnotation = typeAliasScopes[ii][id.name];
if (typeAliasAnnotation) {
return typeAliasAnnotation;
}
}
return null;
}
exports.initTypeAliasTracking = initTypeAliasTracking;
exports.pushTypeAliases = pushTypeAliases;
exports.popTypeAliases = popTypeAliases;
/*----- Tracking which TypeVariables are in scope -----*/
// Counts how many scopes deep each type variable is
function initTypeVariableScopeTracking(state) {
state.g.typeVariableScopeDepth = {};
}
function pushTypeVariables(node, state) {
var parameterDeclaration = node.typeParameters, scopeHistory;
if (parameterDeclaration != null
&& parameterDeclaration.type === Syntax.TypeParameterDeclaration) {
parameterDeclaration.params.forEach(function (id) {
scopeHistory = state.g.typeVariableScopeDepth[id.name] || 0;
state.g.typeVariableScopeDepth[id.name] = scopeHistory + 1;
});
}
}
function popTypeVariables(node, state) {
var parameterDeclaration = node.typeParameters, scopeHistory;
if (parameterDeclaration != null
&& parameterDeclaration.type === Syntax.TypeParameterDeclaration) {
parameterDeclaration.params.forEach(function (id) {
scopeHistory = state.g.typeVariableScopeDepth[id.name];
state.g.typeVariableScopeDepth[id.name] = scopeHistory - 1;
});
}
}
function isTypeVariableInScope(id, state) {
return state.g.typeVariableScopeDepth[id.name] > 0;
}
exports.initTypeVariableScopeTracking = initTypeVariableScopeTracking;
exports.pushTypeVariables = pushTypeVariables;
exports.popTypeVariables = popTypeVariables;
/*----- FromFlowToTypechecks -----*/
function fromFlowAnnotation(/*object*/ annotation, state) /*?object*/ {
var ast;
switch (annotation.type) {
case "NumberTypeAnnotation":
return createAst(SYMBOLS.SIMPLE, "number", 0);
case "StringTypeAnnotation":
return createAst(SYMBOLS.SIMPLE, "string", 0);
case "BooleanTypeAnnotation":
return createAst(SYMBOLS.SIMPLE, "boolean", 0);
case "AnyTypeAnnotation": // fallthrough
case "VoidTypeAnnotation":
return null;
case "NullableTypeAnnotation":
ast = fromFlowAnnotation(annotation.typeAnnotation, state);
if (ast) {
ast.nullable = true;
}
return ast;
case 'ObjectTypeAnnotation':
// ObjectTypeAnnotation is always converted to a simple object type, as we
// don't support records
return createAst(SYMBOLS.SIMPLE, 'object', 0);
case 'FunctionTypeAnnotation':
var params = annotation.params
.map(function(param) {
return fromFlowAnnotation(param.typeAnnotation, state);
})
.filter(function(ast) {
return !!ast;
});
var returnType = fromFlowAnnotation(annotation.returnType, state);
// If any of the params have a type that cannot be expressed, then we have
// to render a simple function instead of a detailed one
if ((params.length || returnType)
&& params.length === annotation.params.length) {
return createAst(SYMBOLS.FUNCTION, [params, returnType], 0)
}
return createAst(SYMBOLS.SIMPLE, 'function', 0);
case "GenericTypeAnnotation":
var alias = getTypeAlias(annotation.id, state);
if (alias) {
return fromFlowAnnotation(alias, state);
}
// Qualified type identifiers are not handled by runtime typechecker,
// so simply omit the annotation for now.
if (annotation.id.type === "QualifiedTypeIdentifier") {
return null;
}
if (isTypeVariableInScope(annotation.id, state)) {
return null;
}
var name = annotation.id.name;
var nameLowerCased = name.toLowerCase();
if (name !== 'Object' && BLACKLISTED.hasOwnProperty(name)) {
return null;
}
if (SIMPLETYPES.hasOwnProperty(nameLowerCased)) {
name = nameLowerCased;
}
var id = createAst(
SYMBOLS.SIMPLE,
name,
0
);
switch (name) {
case "mixed": // fallthrough
case "$Enum":
// Not supported
return null;
case "array": // fallthrough
case "promise":
if (annotation.typeParameters) {
var parametricAst = fromFlowAnnotation(
annotation.typeParameters.params[0],
state
);
if (parametricAst) {
return createAst(
SYMBOLS.GENERIC,
[id, parametricAst],
0
);
}
}
break;
case '$Either':
if (annotation.typeParameters) {
return createAst(
SYMBOLS.UNION,
annotation.typeParameters.params.map(
function (node) { return fromFlowAnnotation(node, state); }
),
0
);
}
return null;
}
return id;
}
return null;
}
exports.fromFlow = function(/*object*/ annotation, state) /*?string*/ {
var ast = fromFlowAnnotation(annotation, state);
return ast ? compile(ast) : null;
};

View File

@ -0,0 +1,276 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*jslint node: true */
"use strict";
var esprima = require('esprima');
var Syntax = esprima.Syntax;
var traverseFlat = require('./traverseFlat');
/**
* If the expression is an identifier, it is resolved in the scope chain.
* If it is an assignment expression, it resolves to the right hand side.
*
* In all other cases the expression itself is returned.
*
* Since the scope chain constructed by the traverse function is very simple
* (it doesn't take into account *changes* to the variable through assignment
* statements), this function doesn't return the correct value in every
* situation. But it's good enough for how it is used in the parser.
*
* @param {object} expr
* @param {array} scopeChain
*
* @return {object}
*/
function resolveToValue(expr, scopeChain) {
switch (expr.type) {
case Syntax.AssignmentExpression:
if (expr.operator === '=') {
return resolveToValue(expr.right, scopeChain);
}
break;
case Syntax.Identifier:
var value;
scopeChain.some(function(scope, i) {
if (hasOwnProperty.call(scope, expr.name) && scope[expr.name]) {
value = resolveToValue(scope[expr.name], scopeChain.slice(i));
return true;
}
});
return value;
}
return expr;
}
/**
* Returns true if the statement is of form `foo = bar;`.
*
* @param {object} node
* @return {bool}
*/
function isAssignmentStatement(node) {
return node.type === Syntax.ExpressionStatement &&
node.expression.type === Syntax.AssignmentExpression &&
node.expression.operator === '=';
}
/**
* Splits a member or call expression into parts. E.g. foo.bar.baz becomes
* ['foo', 'bar', 'baz']
*
* @param {object} expr
* @return {array}
*/
function expressionToArray(expr) {
var parts = [];
switch(expr.type) {
case Syntax.CallExpression:
parts = expressionToArray(expr.callee);
break;
case Syntax.MemberExpression:
parts = expressionToArray(expr.object);
if (expr.computed) {
parts.push('...');
} else {
parts.push(expr.property.name || expr.property.value);
}
break;
case Syntax.Identifier:
parts = [expr.name];
break;
case Syntax.Literal:
parts = [expr.raw];
break;
case Syntax.ThisExpression:
parts = ['this'];
break;
case Syntax.ObjectExpression:
var properties = expr.properties.map(function(property) {
return expressionToString(property.key) +
': ' +
expressionToString(property.value);
});
parts = ['{' + properties.join(', ') + '}'];
break;
case Syntax.ArrayExpression:
parts = ['[' + expr.elements.map(expressionToString).join(', ') + ']'];
break;
}
return parts;
}
/**
* Creates a string representation of a member expression.
*
* @param {object} expr
* @return {array}
*/
function expressionToString(expr) {
return expressionToArray(expr).join('.');
}
/**
* Returns true if the expression is of form `exports.foo = bar;` or
* `modules.exports = foo;`.
*
* @param {object} node
* @return {bool}
*/
function isExportsOrModuleExpression(expr) {
if (expr.left.type !== Syntax.MemberExpression) {
return false;
}
var exprArr = expressionToArray(expr.left);
return (exprArr[0] === 'module' && exprArr[1] === 'exports') ||
exprArr[0] == 'exports';
}
/**
* Finds module.exports / exports.X statements inside an assignment expression.
*/
function handleAssignmentExpression(expr, scopeChain, multipleExports) {
while (!isExportsOrModuleExpression(expr)) {
if (expr.type === Syntax.AssignmentExpression &&
expr.right.type === Syntax.AssignmentExpression) {
expr = expr.right;
} else {
return;
}
}
var definition = resolveToValue(
expr.right,
scopeChain
);
if (!definition) {
// handle empty var declaration, e.g. "var x; ... module.exports = x"
if (expr.right.type === Syntax.Identifier) {
var found = false;
scopeChain.some(function(scope) {
if (scope[expr.right.name] === null) {
return found = true;
}
});
if (found) {
// fake definition so we still return something at least
return {
definition: {
type: Syntax.VariableDeclaration,
loc: expr.loc,
isSynthesized: true
},
scopeChain: scopeChain
};
}
}
return;
}
var leftExpression = expr.left;
var leftExpressions = expressionToArray(leftExpression);
if (leftExpressions[0] === 'exports') {
// exports.A = A
if (leftExpressions.length === 2 && leftExpression.property) {
// The 2nd element is the field name
multipleExports.push({
key: leftExpression.property,
value: definition
});
}
} else if (definition) {
// module.exports = A
return {
definition: definition,
scopeChain: scopeChain
};
}
}
/**
* Given an AST, this function tries to find the object expression that is the
* module's exported value.
*
* @param {object} ast
* @return {?object}
*/
function findExportDefinition(ast) {
var multipleExports = [];
var singleExport;
traverseFlat(ast, function(node, scopeChain) {
if (singleExport) {
return false;
}
if (node.type === Syntax.VariableDeclaration) {
node.declarations.forEach(function (decl) {
if (!singleExport && decl.init &&
decl.init.type === Syntax.AssignmentExpression) {
singleExport = handleAssignmentExpression(
decl.init,
scopeChain,
multipleExports
);
}
});
return false;
}
if (!isAssignmentStatement(node)) {
return false;
}
if (node.expression) {
singleExport = handleAssignmentExpression(
node.expression,
scopeChain,
multipleExports
);
}
});
// NOT going to handle the f**ked up case where in the same file we have
// module.exports = A; exports.b = b;
if (singleExport) {
return singleExport;
}
if (multipleExports.length === 1) {
return {
scopeChain: [],
definition: multipleExports[0].value
};
}
if (multipleExports.length > 0) {
// Synthesize an ObjectExpression union all exports
var properties = multipleExports.map(function(element) {
var key = element.key;
var value = element.value;
return {
type: Syntax.Property,
key: key,
value: value,
loc: {
start: { line: key.loc.start.line, column: key.loc.start.column },
end: { line: value.loc.end.line, column: value.loc.end.column }
},
range: [ key.range[0], value.range[1] ]
};
});
return {
scopeChain: [],
definition: {
isSynthesized: true,
type: Syntax.ObjectExpression,
properties: properties,
// Use the first export statement location
loc: properties[0].loc
}
};
}
return null;
};
module.exports = findExportDefinition;

View File

@ -0,0 +1,534 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*global exports:true*/
/*jslint node:true*/
"use strict";
var util = require('util');
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');
// Transforms
var meta = require('./meta');
var type = require('./type');
var typeHintExp = /^\??[\w<>|:(),?]+$/;
var paramRe = /\*\s+@param\s+{?([^\s*{}.]+)}?(\s+([\w\$]+))?/g;
var returnRe = /\*\s+@return(s?)\s+{?([^\s*{}.]+)}?/;
var nameToTransforms = {
'sourcemeta': meta,
'typechecks': type,
};
var excludes = [];
function getTypeHintsFromDocBlock(node, docBlocksByLine) {
var comments = docBlocksByLine[node.loc.start.line - 1];
if (!comments) {
return {
params: null,
returns: null
};
}
var params = [];
if (node.params) {
var paramNames = node.params.reduce(function(map, param) {
map[param.name] = true;
return map;
}, {});
var param;
while(param = paramRe.exec(comments.value)) {
if (!param[1]) {
continue;
}
var functionName = node.id
? '`' + node.id.name + '\''
: '<anonymous>';
if (!param[3]) {
throw new Error(util.format('Lines: %s-%s: Your @param declaration in' +
' function %s is missing the parameter\'s name,' +
' i.e. "@param {string} name"',
comments.loc.start.line, comments.loc.end.line, functionName));
}
// TODO(ostrulovich) if we're really nice, we should probably check edit
// distance and suggest the right name the user meant
if (!(param[3] in paramNames)) {
throw new Error(util.format('Lines: %s-%s: `%s\' is not a valid ' +
'formal parameter of function %s. Must be one of: %s',
comments.loc.start.line, comments.loc.end.line, param[3],
functionName, Object.keys(paramNames).join(', ')));
}
params.push([param[3], param[1]]);
}
}
var returnType = returnRe.exec(comments.value);
if (returnType && returnType[1]) {
throw new Error(util.format('Lines: %s-%s: Your @return declaration in' +
' function %s is incorrectly written as @returns. Remove the trailing'+
' \'s\'.',
comments.loc.start.line, comments.loc.end.line, functionName));
}
return {
params: params.length ? params : null,
returns: returnType ? returnType[2] : null
};
}
function getTypeHintFromInline(node, commentsByLine) {
var key = node.loc.start.column - 1;
var comments = commentsByLine[node.loc.start.line];
if (!comments || !(key in comments)) {
return null;
}
// annotate the node
node.typeHint = comments[key].value;
return node.typeHint;
}
/**
* Parses out comments from AST
* and populates commentsByLine and docBlocksByLine
*/
function parseComments(programNode, state) {
programNode.comments.forEach(function(c) {
if (c.type !== 'Block') return;
var comments;
if (c.loc.start.line === c.loc.end.line &&
typeHintExp.test(c.value)) {
// inline comments
comments = state.commentsByLine[c.loc.start.line] ||
(state.commentsByLine[c.loc.start.line] = {});
comments[c.loc.end.column] = c;
comments = state.commentsByLine[c.loc.end.line] ||
(state.commentsByLine[c.loc.start.line] = {});
comments[c.loc.end.column] = c;
} else {
// docblocks
state.docBlocksByLine[c.loc.end.line] = c;
}
});
}
function getTypeHintParams(node, state) {
// First look for typehints in the docblock.
var typeHints = getTypeHintsFromDocBlock(node, state.docBlocksByLine);
// If not found, look inline.
if (!typeHints.params && node.params) {
typeHints.params = node.params.map(function(param, index) {
return [param.name, getTypeHintFromInline(param, state.commentsByLine)];
}).filter(function(param) {
return param[1];
});
}
if (!typeHints.returns) {
typeHints.returns = getTypeHintFromInline(node.body, state.commentsByLine);
}
return typeHints;
}
/**
* Get parameters needed for the dynamic typehint checks.
*/
function normalizeTypeHintParams(node, state, typeHints) {
var preCond = [];
if (typeHints.params.length > 0) {
typeHints.params.forEach(function(typeHint) {
if (typeHint[1]) {
preCond.push([
typeHint[0],
'\''+ type.parseAndNormalize(typeHint[1], typeHint[0], node) +'\'',
'\''+ typeHint[0] +'\''
]);
}
});
}
var postCond = null;
if (typeHints.returns) {
postCond = type.parseAndNormalize(typeHints.returns, 'return', node);
}
// If static-only, then we don't need to pass the type hint
// params since we're not going to do any dynamic checking.
var pragmas = utils.getDocblock(state);
if ('static-only' in pragmas) {
return null;
}
var typeHintParams = {};
if (preCond.length > 0) {
typeHintParams.params = preCond;
}
if (postCond) {
typeHintParams.returns = postCond;
}
return (preCond.length || postCond) ? typeHintParams : null;
}
/**
* Takes in all the various params on the function in the docblock or inline
* comments and converts them into the format the bodyWrapper transform is
* expecting. If there are no params needed, returns null.
*
* For example, for a docblock like so
* @param {string} x
* @param {number} y
* @return {number}
* the resulting params object would contain
* {
* params: [ [ 'x', 'number' ], [ 'y', 'number' ] ],
* returns: 'number'
* }
*
* However the bodyWrapper transform expects input like
* {
* params:
* [ [ 'x', '\'number\'', '\'x\'' ],
* [ 'y', '\'number\'', '\'y\'' ] ],
* returns: 'number'
* }
*/
function formatBodyParams(node, state, params) /*?object*/ {
return normalizeTypeHintParams(node, state, params);
}
/**
* Takes in all the various params on the function in the docblock or inline
* comments and converts them into the format the annotator transform is
* expecting. If there are no params needed, returns null.
*
* For example, for a docblock like so
* @param {string} x
* @param {number} y
* @return {number}
* the resulting params object would contain
* {
* params: [ [ 'x', 'number' ], [ 'y', 'number' ] ],
* returns: 'number'
* }
*
* However the bodyWrapper transform expects input like
* {
* params: [ 'number', 'number' ],
* returns: 'number'
* }
*/
function formatAnnotatorParams(params) /*?object*/ {
if ((!params.params || params.params.length === 0) && !params.returns) {
return null;
}
var annotatorParams = {};
if (params.params && params.params.length > 0) {
var paramTypes = [];
params.params.forEach(function(paramArray) {
paramTypes.push(paramArray[1]);
});
annotatorParams.params = paramTypes;
}
if (params.returns) {
annotatorParams.returns = params.returns;
}
return annotatorParams;
}
/**
* Function used for determining how the params will be inlined
* into the function transform. We can't just use utils.format
* with %j because the way the data is stored in params vs
* formatted is different.
*/
function renderParams(/*?object*/ params) /*string*/ {
if (params == null) {
return null;
}
var formattedParams = [];
if (params.params && params.params.length > 0) {
var preCond = params.params;
var joined = preCond.map(function(cond) {
return '[' + cond.join(', ') + ']';
}).join(', ');
var paramString = '\"params\":' + '[' + joined + ']';
formattedParams.push(paramString);
}
if (params.returns) {
var returnParam = '\"returns\":' + '\'' + params.returns + '\'';
formattedParams.push(returnParam);
}
return "{" + formattedParams.join(',') + "}";
}
function getModuleName(state) {
var docblock = utils.getDocblock(state);
return docblock.providesModule || docblock.providesLegacy;
}
function getFunctionMetadata(node, state) {
var funcMeta = {
module: getModuleName(state),
line: node.loc.start.line,
column: node.loc.start.column,
name: node.id && node.id.name
};
if (!funcMeta.name) {
delete funcMeta.name;
}
return funcMeta;
}
function getNameToTransforms() {
var filtered = {};
Object.keys(nameToTransforms).forEach(function(name) {
if (excludes.indexOf(name) == -1) {
filtered[name] = nameToTransforms[name];
}
});
return filtered;
}
/**
* Returns true if there are any transforms that would want to modify the
* current source. Usually we can rule out some transforms because the top
* pragma may say @nosourcemeta or there isn't a @typechecks. This function is
* used to rule out sources where no transform applies.
*
* @param {object} state
* @param {object} pragmas
* @return {bool}
*/
function shouldTraverseFile(state, pragmas) {
var t = false;
var nameToTransforms = getNameToTransforms();
Object.keys(nameToTransforms).forEach(function(value) {
var transform = nameToTransforms[value];
t = t || transform.shouldTraverseFile(state, pragmas);
});
return t;
}
/**
* Collects all the necessary information from the docblock and inline comments
* that may be useful to a transform. Currently only the type transform has
* information like @param and @return or the inline comments.
*/
function getAllParams(node, state) {
if (type.shouldTransformFile(state, utils.getDocblock(state))) {
return getTypeHintParams(node, state);
}
return {};
}
/**
* Returns an array of transforms that return true when shouldTransformFile is
* called.
*/
function getTransformsForFile(state, pragmas) {
var transforms = [];
var nameToTransforms = getNameToTransforms();
Object.keys(nameToTransforms).forEach(function(value) {
var transform = nameToTransforms[value];
if (transform.shouldTransformFile(state, pragmas)) {
transforms.push(transform);
}
});
return transforms;
}
/**
* Returns an array of trasnforms that return true when
* shouldTransformFunction is called.
*/
function getTransformsForFunction(transformsForFile, node, state, pragmas,
params) {
var transforms = [];
transformsForFile.forEach(function(transform) {
if (transform.shouldTransformFunction(node, state, pragmas, params)) {
transforms.push(transform);
}
});
return transforms;
}
/**
* This function will perform any checks over the JS source that doesn't
* require injecting in source code. For example the typechecks transform
* has a mode called static-only that does not add any extra code.
*/
function processStaticOnly(node, state) {
var pragmas = utils.getDocblock(state);
if (pragmas.typechecks === 'static-only') {
var params = getTypeHintParams(node, state);
normalizeTypeHintParams(node, state, params);
}
}
function shouldWrapBody(transformsForFile) {
var t = false;
transformsForFile.forEach(function(transform) {
t = t || transform.wrapsBody();
});
return t;
}
function shouldAnnotate(transformsForFile) {
var t = false;
transformsForFile.forEach(function(transform) {
t = t || transform.annotates();
});
return t;
}
/**
* Gets the trailing arguments string that should be appended to
* __annotator(foo,
* and does not include a semicolon.
*/
function getTrailingAnnotatorArguments(funcMeta, annotatorParams) {
if (annotatorParams === null) {
return util.format(', %j)', funcMeta);
}
return util.format(', %j, %j)', funcMeta, annotatorParams);
}
/**
* This is the main entry point into the generic function transforming.
*/
function genericFunctionTransformer(traverse, node, path, state) {
// The typechecks transform has a static-only mode that doesn't actually
// perform a transform but validates the types.
processStaticOnly(node, state, params);
var params = getAllParams(node, state);
var transformsForFile = getTransformsForFile(state, utils.getDocblock(state));
var transformsForFunction =
getTransformsForFunction(
transformsForFile,
node,
state,
utils.getDocblock(state),
params
);
if (transformsForFunction.length === 0) {
traverse(node.body, path, state);
return;
}
var wrapBody = shouldWrapBody(transformsForFunction);
var annotate = shouldAnnotate(transformsForFunction);
// There are two different objects containing the params for the wrapper
// vs annotator because the type param information only makes sense inside
// the body wrapper like [x, 'number', 'x']. During execution the body wrapper
// will be passed the correct values whereas during the annotator the
// arguments don't exist yet.
var bodyParams = wrapBody ? formatBodyParams(node, state, params) : null;
var annotatorParams = annotate ? formatAnnotatorParams(params) : null;
var funcMeta = getFunctionMetadata(node, state);
// If there are no params to pass to the body, then don't wrap the
// body function.
wrapBody = wrapBody && bodyParams !== null;
var renderedBodyParams = renderParams(bodyParams);
if (node.type === Syntax.FunctionExpression && annotate) {
utils.append('__annotator(', state);
}
// Enter function body.
utils.catchup(node.body.range[0] + 1, state);
// Insert a function that wraps the function body.
if (wrapBody) {
utils.append(
'return __bodyWrapper(this, arguments, function() {',
state
);
}
// Recurse down into the child.
traverse(node.body, path, state);
// Move the cursor to the end of the function body.
utils.catchup(node.body.range[1] - 1, state);
// Close the inserted function.
if (wrapBody) {
utils.append(util.format('}, %s);', renderedBodyParams), state);
}
// Write the closing } of the function.
utils.catchup(node.range[1], state);
if (!annotate) {
return;
}
if (node.type === Syntax.FunctionExpression) {
utils.append(
getTrailingAnnotatorArguments(funcMeta, annotatorParams),
state
);
} else if (node.type === Syntax.FunctionDeclaration) {
utils.append(
util.format(
'__annotator(%s',
node.id.name
) + getTrailingAnnotatorArguments(funcMeta, annotatorParams) + ';',
state
);
}
}
function visitFunction(traverse, node, path, state) {
if (node.type === Syntax.Program) {
state.docBlocksByLine = {};
state.commentsByLine = {};
parseComments(node, state);
return;
}
genericFunctionTransformer(traverse, node, path, state);
return false;
}
visitFunction.test = function(node, path, state) {
var pragmas = utils.getDocblock(state);
if (!shouldTraverseFile(state, pragmas)) {
return false;
}
switch (node.type) {
case Syntax.Program:
case Syntax.FunctionExpression:
case Syntax.FunctionDeclaration:
return true;
default:
return false;
}
};
function setExcludes(excl) {
excludes = excl;
}
exports.visitorList = [visitFunction];
exports.setExcludes = setExcludes;
exports.formatAnnotatorParams = formatAnnotatorParams;
exports.getTrailingAnnotatorArguments = getTrailingAnnotatorArguments;
exports.getTypeHintsFromDocBlock = getTypeHintsFromDocBlock;
exports.getTypeHintFromInline = getTypeHintFromInline;

519
website/jsdocs/jsdocs.js Normal file
View File

@ -0,0 +1,519 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*jslint node: true */
"use strict";
var esprima = require('esprima');
var fs = require('fs');
var Syntax = esprima.Syntax;
var findExportDefinition = require('./findExportDefinition');
var genericTransform = require('./generic-function-visitor');
var genericVisitor = genericTransform.visitorList[0];
var traverseFlat = require('./traverseFlat')
var parseTypehint = require('./TypeExpressionParser').parse;
// Don't save object properties source code that is longer than this
var MAX_PROPERTY_SOURCE_LENGTH = 1000;
function invariant(condition, message) {
if (!condition) {
throw new Error(message);
}
}
/**
* If the expression is an identifier, it is resolved in the scope chain.
* If it is an assignment expression, it resolves to the right hand side.
*
* In all other cases the expression itself is returned.
*
* Since the scope chain constructed by the traverse function is very simple
* (it doesn't take into account *changes* to the variable through assignment
* statements), this function doesn't return the correct value in every
* situation. But it's good enough for how it is used in the parser.
*
* @param {object} expr
* @param {array} scopeChain
*
* @return {object}
*/
function resolveToValue(expr, scopeChain) {
switch (expr.type) {
case Syntax.AssignmentExpression:
if (expr.operator === '=') {
return resolveToValue(expr.right, scopeChain);
}
break;
case Syntax.Identifier:
var value;
scopeChain.some(function(scope, i) {
if (hasOwnProperty.call(scope, expr.name) && scope[expr.name]) {
value = resolveToValue(scope[expr.name], scopeChain.slice(i));
return true;
}
});
return value;
}
return expr;
}
/**
* Strips the "static upstream" warning from the docblock.
*
* @param {?string} docblock
* @return {?string}
*/
function stripStaticUpstreamWarning(docblock) {
if (!docblock) {
return docblock;
}
// Esprima strips out the starting and ending tokens, so add them back
docblock = "/*" + docblock + "*/\n";
return docblock;
}
/**
* Parse a typehint, but swallow any errors.
*/
function safeParseTypehint(typehint) {
if (!typehint) {
return null;
}
try {
return JSON.stringify(parseTypehint(typehint));
} catch (e) {
return null;
}
}
/**
* Gets the docblock for the file
*
* @param {array<object>} commentsForFile
* @return {?string}
*/
function getFileDocBlock(commentsForFile) {
var docblock;
commentsForFile.some(function(comment, i) {
if (comment.loc.start.line === 1) {
var lines = comment.value.split("\n");
var filteredLines = lines.filter(function(line) {
var hasCopyright = !!line.match(/^\s*\*\s+Copyright/);
var hasProvides = !!line.match(/^\s*\*\s+@provides/);
return !hasCopyright && !hasProvides;
});
docblock = filteredLines.join("\n");
return true;
}
});
return stripStaticUpstreamWarning(docblock);
}
/**
* Gets the docblock for a given node.
*
* @param {object} Node to get docblock for
* @param {array<object>} commentsForFile
* @param {array<string>} linesForFile
* @return {?string}
*/
function getDocBlock(node, commentsForFile, linesForFile) {
if (node.isSynthesized === true) {
return '';
}
var docblock;
var prevLine = node.loc.start.line - 1;
// skip blank lines
while (linesForFile[prevLine - 1].trim() === '') {
prevLine--;
}
commentsForFile.some(function(comment, i) {
if (comment.loc.end.line === prevLine) {
if (comment.type === 'Line') {
// Don't accept line comments that are separated
if (prevLine !== node.loc.start.line - 1) {
return true;
}
var line = prevLine;
docblock = '';
for (var ii = i; ii >= 0; ii--) {
var lineComment = commentsForFile[ii];
if (lineComment.loc.end.line === line) {
docblock = '//' + lineComment.value +
(docblock ? "\n" + docblock : "");
line--;
} else {
break;
}
}
} else {
docblock = stripStaticUpstreamWarning(comment.value);
}
return true;
}
});
return docblock;
}
/**
* Given the comments for a file, return the module name (by looking for
* @providesModule).
*
* @param {array<object>}
* @return {?string}
*/
function getModuleName(commentsForFile) {
var moduleName;
commentsForFile.forEach(function(comment) {
if (comment.type === 'Block') {
var matches = comment.value.match(/@providesModule\s+(\S*)/);
if (matches && matches[1]) {
moduleName = matches[1];
}
}
});
return moduleName;
}
/**
* Esprima includes the leading colon (and possibly spaces) as part of the
* typehint, so we have to strip those out.
*/
function sanitizeTypehint(string) {
for (var i = 0; i < string.length; i++) {
if (string[i] != ' ' && string[i] != ':') {
return string.substring(i);
}
}
return null;
}
/**
* @param {object} node
* @param {object} state
* @param {string} source
* @param {array<object>} commentsForFile
* @param {array<string>} linesForFile
* @return {object}
*/
function getFunctionData(node, state, source, commentsForFile, linesForFile) {
var params = [];
var typechecks = commentsForFile.typechecks;
var typehintsFromBlock = null;
if (typechecks) {
// esprima has trouble with some params so ignore them (e.g. $__0)
if (!node.params.some(function(param) { return !param.name; })) {
try {
typehintsFromBlock = genericTransform.getTypeHintsFromDocBlock(
node,
state.docBlocksByLine
);
} catch (e) {
}
}
}
node.params.forEach(function(param) {
// TODO: Handle other things like Syntax.ObjectPattern
if (param.type === Syntax.Identifier) {
var typehint;
if (typehintsFromBlock && typehintsFromBlock.params) {
typehintsFromBlock.params.some(function(paramTypehint) {
if (paramTypehint[0] === param.name) {
typehint = paramTypehint[1];
return true;
}
});
}
if (!typehint && typechecks) {
try {
typehint = genericTransform.getTypeHintFromInline(
param,
state.commentsByLine
);
} catch (e) {
}
}
params.push({
typehint: safeParseTypehint(typehint),
name: param.name
});
} else if (param.type === Syntax.TypeAnnotatedIdentifier) {
params.push({
typehint: sanitizeTypehint(source.substring(
param.annotation.range[0],
param.annotation.range[1]
)),
name: param.id.name
});
}
});
var returnTypehint = null;
if (node.returnType) {
returnTypehint = sanitizeTypehint(source.substring(
node.returnType.range[0],
node.returnType.range[1]
));
} else if (typehintsFromBlock) {
returnTypehint = typehintsFromBlock.returns;
}
return {
line: node.loc.start.line,
source: source.substring.apply(source, node.range),
docblock: getDocBlock(node, commentsForFile, linesForFile),
modifiers: [],
params: params,
returntypehint: safeParseTypehint(returnTypehint)
};
}
/**
* @param {object} node
* @param {object} state
* @param {string} source
* @param {array<object>} commentsForFile
* @param {array<string>} linesForFile
* @return {object}
*/
function getObjectData(node, state, source, scopeChain,
commentsForFile, linesForFile) {
var methods = [];
var properties = [];
var superClass = null;
node.properties.forEach(function(property) {
if (property.type === Syntax.SpreadProperty) {
if (property.argument.type === Syntax.Identifier) {
superClass = property.argument.name;
}
return;
}
switch (property.value.type) {
case Syntax.FunctionExpression:
var methodData = getFunctionData(property.value, state, source,
commentsForFile, linesForFile);
methodData.name = property.key.name || property.key.value;
methodData.source = source.substring.apply(source, property.range);
methodData.modifiers.push('static');
methods.push(methodData);
break;
case Syntax.Identifier:
var expr = resolveToValue(
property.value,
scopeChain
);
if (expr) {
if (expr.type === Syntax.FunctionDeclaration) {
var functionData =
getFunctionData(expr, state, source, commentsForFile, linesForFile);
functionData.name = property.key.name || property.key.value;
functionData.modifiers.push('static');
methods.push(functionData);
break;
} else {
property.value = expr;
}
}
/* falls through */
default:
var propertySource = '';
var valueRange = property.value.range;
if (valueRange[1] - valueRange[0] < MAX_PROPERTY_SOURCE_LENGTH) {
propertySource = source.substring.apply(source, valueRange);
}
var docBlock = getDocBlock(property, commentsForFile, linesForFile);
/* CodexVarDef: modifiers, type, name, default, docblock */
var propertyData = [
['static'],
'',
// Cast to String because this can be a Number
// Could also be a String literal (e.g. "key") hence the value
String(property.key.name || property.key.value),
propertySource,
docBlock || '',
property.loc.start.line
];
properties.push(propertyData);
break;
}
});
return {
methods: methods,
properties: properties,
superClass: superClass
};
}
/**
* @param {object} node
* @param {object} state
* @param {string} source
* @param {array<object>} commentsForFile
* @param {array<string>} linesForFile
* @return {object}
*/
function getClassData(node, state, source, commentsForFile, linesForFile) {
var methods = [];
invariant(node.body.type === Syntax.ClassBody, 'Expected ClassBody');
node.body.body.forEach(function(bodyItem) {
if (bodyItem.type === Syntax.MethodDefinition) {
if (bodyItem.value.type === Syntax.FunctionExpression) {
var methodData =
getFunctionData(bodyItem.value, state, source,
commentsForFile, linesForFile);
methodData.name = bodyItem.key.name;
methodData.source = source.substring.apply(source, bodyItem.range);
if (bodyItem.static) {
methodData.modifiers.push('static');
}
methods.push(methodData);
}
}
});
var data = {
methods: methods
};
if (node.superClass && node.superClass.type === Syntax.Identifier) {
data.superClass = node.superClass.name;
}
return data;
}
/**
* Finds all the requires
*
* @param {object} ast
* @return {array<object>}
*/
function findRequires(ast) {
var requires = [];
traverseFlat(ast, function(node, scopeChain) {
var requireData = getRequireData(node);
if (requireData) {
requires.push(requireData);
}
return !requireData;
});
return requires;
}
/**
* If the given node is a 'require' statement, returns a list of following data
* {
* name: string
* }
*
* @return ?object
*/
function getRequireData(node) {
if (!node || node.type !== Syntax.CallExpression) {
return null;
}
var callee = node.callee;
if (callee.type !== Syntax.Identifier
|| (callee.name !== 'require')) {
return null;
}
var args = node['arguments'];
if (args.length === 0) {
return null;
}
var firstArgument = args[0];
if (firstArgument.type !== Syntax.Literal) {
return null;
}
return {
name: firstArgument.value
};
}
/**
* Given the source of a file, this returns the data about the module's exported
* value.
*
* @param {string} source
* @return {?object} data
*/
function parseSource(source) {
var lines = source.split("\n");
var ast = esprima.parse(source, {
loc: true,
comment: true,
range: true,
sourceType: 'nonStrictModule',
});
/**
* This sets up genericTransform so that it can be queried above.
*/
var _state = {
g: {
source: source
}
};
if (genericVisitor.test(ast, [], _state)) {
// HACK: Mark that this file has typechecks on the comments object.
ast.comments.typechecks = true;
// This fills out the data for genericTransform.
genericVisitor(function() {}, ast, [], _state);
}
var result = findExportDefinition(ast.body);
if (result) {
var definition = result.definition;
var scopeChain = result.scopeChain;
var data;
var moduleName = getModuleName(ast.comments);
if (!moduleName) {
return null;
}
if (definition.type === Syntax.NewExpression &&
definition.callee.type === Syntax.Identifier) {
var name = definition.callee.name;
// If the class is defined in the scopeChain, export that instead.
scopeChain.some(function(scope) {
if (hasOwnProperty.call(scope, name) &&
scope[name].type === Syntax.ClassDeclaration) {
definition = scope[name];
return true;
}
});
}
console.log(definition.type);
switch (definition.type) {
case Syntax.ClassDeclaration:
data = getClassData(definition, _state, source, ast.comments, lines);
data.type = 'class';
break;
case Syntax.ObjectExpression:
data = getObjectData(definition, _state, source, scopeChain,
ast.comments, lines);
data.type = 'object';
break;
case Syntax.FunctionDeclaration:
case Syntax.FunctionExpression:
data = getFunctionData(definition, _state, source, ast.comments, lines);
data.type = 'function';
break;
default:
data = {type: 'module'};
break;
}
if (data) {
data.line = definition.loc.start.line;
data.name = moduleName;
data.docblock =
getDocBlock(definition, ast.comments, lines) ||
getFileDocBlock(ast.comments);
data.requires = findRequires(ast.body);
return data;
}
}
return null;
}
module.exports = parseSource;

54
website/jsdocs/meta.js Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*global exports:true*/
/*jslint node:true*/
"use strict";
var util = require('util');
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');
// Top level file pragmas that must not exist for the meta transform to
// be applied.
var mustNotHave = [
'nosourcemeta',
];
function shouldTraverseFile(state, pragmas) {
if (state.g.sourcemeta === undefined) {
var notHaves = true;
mustNotHave.forEach(function (value) {
notHaves = notHaves && !(value in pragmas);
});
state.g.sourcemeta = notHaves;
}
return state.g.sourcemeta;
}
var shouldTransformFile = shouldTraverseFile;
function shouldTransformFunction(node, state, pragmas, params) /*bool*/ {
if (!shouldTransformFile(state, pragmas)) {
throw new Error(
'shouldTransformFunction should not be called if shouldTransformFile ' +
'fails'
);
}
return true;
}
function wrapsBody() {
return false;
}
function annotates() {
return true;
}
exports.shouldTransformFile = shouldTransformFile;
exports.shouldTraverseFile = shouldTraverseFile;
exports.shouldTransformFunction = shouldTransformFunction;
exports.wrapsBody = wrapsBody;
exports.annotates = annotates;
exports.name = 'sourcemeta';

View File

@ -0,0 +1,97 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*global exports:true*/
/*jslint node:true*/
"use strict";
var Syntax = require('esprima-fb').Syntax;
/**
* Executes visitor on the object and its children (recursively).
* While traversing the tree, a scope chain is built and passed to the visitor.
*
* If the visitor returns false, the object's children are not traversed.
*
* @param {object} object
* @param {function} visitor
* @param {?array} scopeChain
*/
function traverse(object, visitor, scopeChain) {
scopeChain = scopeChain || [{}];
var scope = scopeChain[0];
switch (object.type) {
case Syntax.VariableDeclaration:
object.declarations.forEach(function(decl) {
scope[decl.id.name] = decl.init;
});
break;
case Syntax.ClassDeclaration:
scope[object.id.name] = object;
break;
case Syntax.FunctionDeclaration:
// A function declaration creates a symbol in the current scope
scope[object.id.name] = object;
/* falls through */
case Syntax.FunctionExpression:
case Syntax.Program:
scopeChain = [{}].concat(scopeChain);
break;
}
if (object.type === Syntax.FunctionExpression ||
object.type === Syntax.FunctionDeclaration) {
// add parameters to the new scope
object.params.forEach(function(param) {
// since the value of the parameters are unknown during parsing time
// we set the value to `undefined`.
scopeChain[0][param.name] = undefined;
});
}
if (object.type) {
if (visitor.call(null, object, scopeChain) === false) {
return;
}
}
for (var key in object) {
if (object.hasOwnProperty(key)) {
var child = object[key];
if (typeof child === 'object' && child !== null) {
traverse(child, visitor, scopeChain);
}
}
}
}
/**
* Executes visitor on the object and its children, but only traverses into
* children which can be statically analyzed and don't depend on runtime
* information.
*
* @param {object} object
* @param {function} visitor
* @param {?array} scopeChain
*/
function traverseFlat(object, visitor, scopeChain) {
traverse(object, function(node, scopeChain) {
switch (node.type) {
case Syntax.FunctionDeclaration:
case Syntax.FunctionExpression:
case Syntax.IfStatement:
case Syntax.WithStatement:
case Syntax.SwitchStatement:
case Syntax.TryStatement:
case Syntax.WhileStatement:
case Syntax.DoWhileStatement:
case Syntax.ForStatement:
case Syntax.ForInStatement:
return false;
}
return visitor(node, scopeChain);
}, scopeChain);
}
module.exports = traverseFlat;

79
website/jsdocs/type.js Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2004-present Facebook. All Rights Reserved.
/*global exports:true*/
"use strict";
var util = require('util');
var Syntax = require('esprima-fb').Syntax;
var utils = require('jstransform/src/utils');
var parse = require('./TypeExpressionParser').parse;
var compile = require('./TypeExpressionParser').compile;
var normalize = require('./TypeExpressionParser').normalize;
function parseAndNormalize(source, name, object) {
if (/\?$/.test(source)) {
source = '?' + source.substring(0, source.length - 1);
}
try {
var ast = parse(source);
return compile(normalize(ast));
} catch (e) {
var functionName = object.id
? '`' + object.id.name + '\''
: '<anonymous>';
throw new Error(util.format('The type `%s\' specified for %s for ' +
'the function %s, on line %s, could not be parsed. The error given was: %s',
source, name, functionName, object.loc.start.line, e.message
));
}
}
function initializeSettings(state, pragmas) {
state.g.typechecks = 'typechecks' in pragmas;
state.g.staticOnly = pragmas.typechecks === 'static-only';
}
function shouldTraverseFile(state, pragmas) {
if (state.g.typechecks === undefined) {
initializeSettings(state, pragmas);
}
return state.g.typechecks;
}
function shouldTransformFile(state, pragmas) {
if (state.g.typechecks === undefined) {
initializeSettings(state, pragmas);
}
return !state.g.staticOnly && state.g.typechecks;
}
function shouldTransformFunction(node, state, pragmas, params) {
if (!shouldTransformFile(state, pragmas)) {
throw new Error(
'shouldTransformFunction should not be called if shouldTransformFile ' +
'fails'
);
}
return (params.params && params.params.length > 0) ||
params.returns ||
(node.id && /^[A-Z]/.test(node.id.name));
}
function wrapsBody() {
return true;
}
function annotates() {
return true;
}
exports.parseAndNormalize = parseAndNormalize;
exports.shouldTransformFile = shouldTransformFile;
exports.shouldTraverseFile = shouldTraverseFile;
exports.shouldTransformFunction = shouldTransformFunction;
exports.wrapsBody = wrapsBody;
exports.annotates = annotates;
exports.name = 'typechecks';

View File

@ -10,6 +10,9 @@
"glob": "*",
"mkdirp": "*",
"request": "*",
"fs.extra": "*"
"fs.extra": "*",
"esprima": "*",
"esprima-fb": "*",
"jstransform": "*"
}
}

View File

@ -2,6 +2,7 @@ var docs = require('../react-docgen');
var fs = require('fs');
var path = require('path');
var slugify = require('../core/slugify');
var jsDocs = require('../jsdocs/jsdocs.js')
function getNameFromPath(filepath) {
var ext = null;
@ -11,7 +12,7 @@ function getNameFromPath(filepath) {
return filepath;
}
function docsToMarkdown(filepath, i) {
function componentsToMarkdown(filepath, i) {
var json = docs.parse(
fs.readFileSync(filepath),
function(node, recast) {
@ -42,11 +43,15 @@ function docsToMarkdown(filepath, i) {
var components = [
'../Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js',
'../Libraries/Components/DatePicker/DatePickerIOS.ios.js',
'../Libraries/Text/ExpandingText.js',
'../Libraries/Image/Image.ios.js',
'../Libraries/Components/ListView/ListView.js',
'../Libraries/Components/Navigation/NavigatorIOS.ios.js',
'../Libraries/Components/ScrollView/ScrollView.js',
'../Libraries/Components/Slider/Slider.js',
'../Libraries/Components/SwitchIOS/SwitchIOS.ios.js',
'../Libraries/Components/TabBarIOS/TabBarIOS.ios.js',
'../Libraries/Text/Text.js',
'../Libraries/Components/TextInput/TextInput.ios.js',
'../Libraries/Components/Touchable/TouchableHighlight.js',
@ -55,6 +60,16 @@ var components = [
'../Libraries/Components/View/View.js',
];
function apisToMarkdown(filepath, i) {
var json = jsDocs(fs.readFileSync(filepath).toString());
console.log(JSON.stringify(json, null, 2));
}
var apis = [
'../Libraries/AppRegistry/AppRegistry.js',
];
module.exports = function() {
return components.map(docsToMarkdown);
return components.map(componentsToMarkdown);
};