mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-26 07:04:05 +08:00
Initial import of the lib to parse javascript code, in the same vein as we parse React proptypes
This commit is contained in:
557
website/jsdocs/TypeExpressionParser.js
Normal file
557
website/jsdocs/TypeExpressionParser.js
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user