UNPKG

babel-plugin-flow-react-proptypes

Version:
779 lines (634 loc) 29.5 kB
"use strict"; var _util = require("./util"); var _convertToPropTypes = _interopRequireDefault(require("./convertToPropTypes")); var _makePropTypesAst = require("./makePropTypesAst"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e2) { throw _e2; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e3) { didErr = true; err = _e3; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } // maps between type alias name to prop types var internalTypes = {}; // maps between type alias to import alias var importedTypes = {}; // maps from imported-name+location to the local name var addedImports = {}; var exportedTypes = {}; var suppress = false; var omitRuntimeTypeExport = false; var SUPPRESS_STRING = 'no babel-plugin-flow-react-proptypes'; // The template to use for the dead code elimination check // if it passes, the prop types will be removed // currently the 'deadCode' option is off by default, but this may change var DEFAULT_DCE = "process.env.NODE_ENV === 'production'"; // General control flow: // Parse flow type annotations in index.js // Convert to intermediate representation via convertToPropTypes.js // Convert to prop-types AST in makePropTypesAst.js // Indicates we shouldn't handle a node again // TODO: use a Symbol or WeakMap var SKIP = "BPFRPT_SKIP"; var convertNodeToPropTypes = function convertNodeToPropTypes(node) { return (0, _convertToPropTypes["default"])(node, importedTypes, internalTypes); }; var getPropsForTypeAnnotation = function getPropsForTypeAnnotation(typeAnnotation) { var props = null; if (typeAnnotation.properties || typeAnnotation.type === 'GenericTypeAnnotation' || typeAnnotation.type === 'IntersectionTypeAnnotation' || typeAnnotation.type === 'AnyTypeAnnotation') { props = convertNodeToPropTypes(typeAnnotation); } else if (typeAnnotation.properties != null || typeAnnotation.type != null) { (0, _util.$debug)('typeAnnotation not of expected type, not generating propTypes: ', typeAnnotation); } else { throw new Error("Expected prop types, but found none. This is a bug in ".concat(_util.PLUGIN_NAME)); } return props; }; module.exports = function flowReactPropTypes(babel) { var t = babel.types; var opts = {}; function shouldUseImport() { return opts.useESModules === true || !opts.deadCode; } var impTemplates = { named: babel.template("import { LOCAL } from 'change me'", { sourceType: 'module' }), renamed: babel.template("import { SOURCE as LOCAL } from 'change me'", { sourceType: 'module' }), "default": babel.template("import NAME from 'change me'", { sourceType: 'module' }), requireDefault: babel.template("require(PATH)"), requireNamed: babel.template("require(PATH).NAME") }; function getFromModule(path, _ref) { var _ref$type = _ref.type, type = _ref$type === void 0 ? 'default' : _ref$type, name = _ref.name, _ref$local = _ref.local, local = _ref$local === void 0 ? name : _ref$local, location = _ref.location; var tid = t.identifier; var tstr = t.stringLiteral; var key = "name:".concat(location, ":").concat(name); if (shouldUseImport()) { if (!addedImports[key]) { var localName = local.replace(/[^a-zA-Z0-9]+/g, '_'); var sourceName = name.replace(/[^a-zA-Z0-9]+/g, '_'); addedImports[key] = localName; var toAdd = null; if (type === 'default') { toAdd = impTemplates["default"]({ NAME: tid(localName) }); } else if (type === 'named') { if (localName === sourceName) { toAdd = impTemplates.named({ LOCAL: tid(localName) }); } else { toAdd = impTemplates.renamed({ LOCAL: tid(localName), SOURCE: tid(sourceName) }); } } if (toAdd) { toAdd.source.value = location; var ppath = path; do { if (ppath.node && ppath.node.type === 'Program') break; } while (ppath = ppath.parentPath); if (ppath && ppath.node.body) { ppath.node.body.push(toAdd); } } } return tid(addedImports[key]); } else { if (type === 'default') { return impTemplates.requireDefault({ PATH: tstr(location) }).expression; } else if (type === 'named') { return impTemplates.requireNamed({ PATH: tstr(location), NAME: tid(name) }).expression; } } } function getFromPropTypesModule(path, name, isRequired) { var ptNode = getFromModule(path, { type: 'default', name: 'PropTypes', location: 'prop-types' }); if (!name) return ptNode; var ptOptional = t.memberExpression(ptNode, t.identifier(name)); if (!isRequired) return ptOptional; return t.memberExpression(ptOptional, t.identifier('isRequired')); } function addExportTypeDecl(path, exportName) { var exportValueNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; if (!exportValueNode) { exportValueNode = t.identifier(exportName); } if (!opts.deadCode || shouldUseImport()) { if (!path.parentPath.isProgram()) return; var body = path.parentPath.node.body; var exportAst = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(exportName), exportValueNode)]); exportAst[SKIP] = true; body.push(exportAst); } else { // add the variable to the exports var _exportAst = t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('defineProperty')), [t.identifier('exports'), t.stringLiteral(exportName), t.objectExpression([t.objectProperty(t.identifier('value'), exportValueNode), t.objectProperty(t.identifier('configurable'), t.booleanLiteral(true))])])); var exportsDefinedCondition = t.binaryExpression('!==', t.unaryExpression('typeof', t.identifier('exports')), t.stringLiteral('undefined')); var ifCond = exportsDefinedCondition; if (opts.deadCode) { var dceConditional = t.unaryExpression('!', getDcePredicate()); ifCond = t.logicalExpression('&&', dceConditional, ifCond); } var conditionalExportsAst = t.ifStatement(ifCond, _exportAst); path.insertAfter(conditionalExportsAst); } } var _templateCache = {}; function getDcePredicate() { // opts.deadCode could be a boolean (true for DEFAULT_DCE), or a string to be // used as a template // if it's falsy, then just return node without any wrapper if (!opts.deadCode) return null; // cache the template since it's going to be used a lot var templateCode = typeof opts.deadCode === 'string' ? opts.deadCode : DEFAULT_DCE; if (!_templateCache[templateCode]) { _templateCache[templateCode] = babel.template(templateCode, { placeholderPattern: false }); } // return a ternary var predicate = _templateCache[templateCode]({}).expression; return predicate; } function wrapInDceCheck(node) { var predicate = getDcePredicate(node); if (!predicate) return node; var conditional = t.conditionalExpression(predicate, t.nullLiteral(), node); return conditional; } var isFunctionalReactComponent = function isFunctionalReactComponent(path) { if ((path.type === 'ArrowFunctionExpression' || path.type === 'FunctionExpression') && !path.parent.id) { // Could be functions inside a React component return false; } if ((0, _util.hasReactElementTypeAnnotationReturn)(path.node)) { return true; } if ((0, _util.containsReactElement)(path.node)) { return true; } return false; }; var findPresetProperties = function findPresetProperties(path, objectName, propertyName) { var propNode = null; var propPath = null; path.traverse({ ClassProperty: function ClassProperty(path) { if (path.node.key && path.node.key.name === propertyName) { propNode = path.node.value; propPath = path; } } }); if (!propNode) { path.parentPath.traverse({ ExpressionStatement: function ExpressionStatement(path) { if (!path.node.expression || !path.node.expression.left) { return; } if (path.node.expression.left.object && path.node.expression.left.property && path.node.expression.left.object.name === objectName && path.node.expression.left.property.name === propertyName) { propNode = path.node.expression.right; propPath = path; } } }); } return [propNode, propPath]; }; var mergeExplicitPropTypes = function mergeExplicitPropTypes(generatedProperties, path, name) { var _findPresetProperties = findPresetProperties(path, name, 'propTypes'), _findPresetProperties2 = _slicedToArray(_findPresetProperties, 2), explicitPropNode = _findPresetProperties2[0], explicitPropPath = _findPresetProperties2[1]; if (!explicitPropNode || !explicitPropNode.properties) { return generatedProperties; } var generatedAndExplicitProperties = generatedProperties.concat(explicitPropNode.properties).reduce(function (acc, i) { if (i.type === 'ObjectProperty') { acc[i.key.name] = i; } else if (i.type === 'SpreadProperty') {// ignore it for now } return acc; }, {}); var mergedPropTypes = Object.keys(generatedAndExplicitProperties).map(function (k) { var original = generatedAndExplicitProperties[k]; // delete original line locations to avoid extra new lines delete original.start; delete original.end; return original; }); explicitPropPath.remove(); return mergedPropTypes; }; var setDefaultPropsOptional = function setDefaultPropsOptional(generatedProperties, path, name) { var _findPresetProperties3 = findPresetProperties(path, name, 'defaultProps'), _findPresetProperties4 = _slicedToArray(_findPresetProperties3, 1), defaultPropNode = _findPresetProperties4[0]; if (!defaultPropNode || !defaultPropNode.properties) { return generatedProperties; } var defaultProps = defaultPropNode.properties.map(function (prop) { return prop.key.name; }); return generatedProperties.map(function (prop) { if (defaultProps.includes(prop.key)) { prop.value.isRequired = false; } return prop; }); }; /** * Adds propTypes or contextTypes annotations to code * * Extracts some shared logic from `annotate`. * * @param path * @param name * @param attribute - target member name ('propTypes' or 'contextTypes') * @param typesOrVar - propsOrVar / contextOrVar value */ var addAnnotationsToAST = function addAnnotationsToAST(path, name, attribute, typesOrVar) { var attachPropTypesAST; // if type was exported, use the declared variable var valueNode = null; if (typeof typesOrVar === 'string') { valueNode = t.identifier(typesOrVar); if (name) { var inner = t.assignmentExpression('=', t.memberExpression(t.identifier(name), t.identifier(attribute)), valueNode); if (attribute === 'propTypes') { inner = wrapInDceCheck(inner); } attachPropTypesAST = t.expressionStatement(inner); } } // type was not exported, generate else { var propTypesAST = (0, _makePropTypesAst.makePropTypesAstForPropTypesAssignment)(typesOrVar); if (propTypesAST == null) { return; } valueNode = propTypesAST; if (attribute === 'propTypes') { valueNode = wrapInDceCheck(valueNode); if (valueNode.properties) { valueNode.properties = mergeExplicitPropTypes(valueNode.properties, path, name); } } attachPropTypesAST = t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier(name), t.identifier(attribute)), valueNode)); } if (!opts.noStatic && (path.type === 'ClassDeclaration' || path.type === 'ClassExpression')) { var newNode = t.classProperty(t.identifier(attribute), valueNode); newNode["static"] = true; path.node.body.body.push(newNode); } else if (attachPropTypesAST) { path.insertAfter(attachPropTypesAST); } }; /** * Called when visiting a node. * * Converts the props param to AST and attaches it at the proper location, * depending on the path param. * * * @param path * @param propsOrVar - props or exported props variable reference * @param contextOrVar - context or exported context variable reference */ var annotate = function annotate(path, propsOrVar) { var contextOrVar = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var name = null; var targetPath; if (!opts.noStatic && (path.type === 'ClassDeclaration' || path.type === 'ClassExpression')) { if (path.node.id) { name = path.node.id.name; } else { // It's an anonymous class but addAnnotationsToAST() needs `name` to be a string anyway name = ""; } targetPath = path; } else if (path.type === 'ArrowFunctionExpression' || path.type === 'FunctionExpression') { name = path.parent.id.name; var basePath = path.parentPath.parentPath; targetPath = t.isExportDeclaration(basePath.parent) ? basePath.parentPath : basePath; } else if (path.node.id) { name = path.node.id.name; targetPath = ['Program', 'BlockStatement'].indexOf(path.parent.type) >= 0 ? path : path.parentPath; } else { throw new Error("babel-plugin-flow-react-proptypes attempted to add propTypes to a function/class with no name"); } if (propsOrVar) { if (propsOrVar.properties) { propsOrVar.properties = setDefaultPropsOptional(propsOrVar.properties, targetPath, name); } addAnnotationsToAST(targetPath, name, 'propTypes', propsOrVar); } if (contextOrVar) { addAnnotationsToAST(targetPath, name, 'contextTypes', contextOrVar); } }; /** * Visitor for functions. * * Determines if a function is a functional react component and * inserts the proptypes and contexttypes AST via `annotate`. * * @param path */ var functionVisitor = function functionVisitor(path) { if (!isFunctionalReactComponent(path)) { return; } // Check if this looks like a stateless react component with PropType reference: var firstParam = path.node.params[0]; var typeAnnotation = firstParam && firstParam.typeAnnotation && firstParam.typeAnnotation.typeAnnotation; // Check if the component has context annotations var secondParam = path.node.params[1]; var contextAnnotation = secondParam && secondParam.typeAnnotation && secondParam.typeAnnotation.typeAnnotation; var propsOrVar = null; if (!typeAnnotation) { (0, _util.$debug)('Found stateless component without type definition'); } else { propsOrVar = typeAnnotation.id && exportedTypes[typeAnnotation.id.name] ? exportedTypes[typeAnnotation.id.name] : getPropsForTypeAnnotation(typeAnnotation); } var contextOrVar; if (contextAnnotation) { contextOrVar = contextAnnotation.id && exportedTypes[contextAnnotation.id.name] ? exportedTypes[contextAnnotation.id.name] : getPropsForTypeAnnotation(contextAnnotation); } else { contextOrVar = null; } if (propsOrVar) { annotate(path, propsOrVar, contextOrVar); } }; return { visitor: { Program: function Program(path, _ref2) { var _opts = _ref2.opts; opts = _opts; internalTypes = {}; importedTypes = {}; exportedTypes = {}; addedImports = {}; (0, _makePropTypesAst.setMakePropTypeImportNode)(function () { return getFromPropTypesModule(path); }); suppress = false; omitRuntimeTypeExport = opts.omitRuntimeTypeExport || false; var directives = path.node.directives; if (directives && directives.length) { var directive = directives[0]; if (directive.value && directive.value.value === SUPPRESS_STRING) { suppress = true; } } if (this.file && this.file.opts && this.file.opts.filename) { if (this.file.opts.filename.indexOf('node_modules') >= 0) { // Suppress any file that lives in node_modules IF the // ignoreNodeModules setting is true suppress = opts.ignoreNodeModules; } } }, "TypeAlias|InterfaceDeclaration|OpaqueType": function TypeAliasInterfaceDeclarationOpaqueType(path) { if (suppress) return; (0, _util.$debug)('TypeAlias/InterfaceDeclaration/OpaqueType found'); var typeAliasName = path.node.id.name; if (!typeAliasName) { throw new Error('Did not find name for type alias'); } var propTypes = convertNodeToPropTypes(path.node); internalTypes[typeAliasName] = propTypes; }, "ClassExpression|ClassDeclaration": function ClassExpressionClassDeclaration(path) { if (opts.noStatic && path.node.type === 'ClassExpression') return; if (path.node[SKIP]) return; path.node[SKIP] = true; if (suppress) return; var superClass = path.node.superClass; // check if we're extending React.Compoennt var extendsReactComponent = superClass && superClass.type === 'MemberExpression' && superClass.object.name === 'React' && (superClass.property.name === 'Component' || superClass.property.name === 'PureComponent'); var extendsComponent = superClass && superClass.type === 'Identifier' && (superClass.name === 'Component' || superClass.name === 'PureComponent'); if (!extendsReactComponent && !extendsComponent) { (0, _util.$debug)('Found a class that isn\'t a react component', superClass); return; } var propTypes = null, contextTypes = null; // And have type as property annotations path.node.body.body.forEach(function (bodyNode) { if (bodyNode && bodyNode.key.name === 'props' && bodyNode.typeAnnotation) { var annotation = bodyNode.typeAnnotation.typeAnnotation; var props = getPropsForTypeAnnotation(annotation); if (!props) { throw new TypeError('Couldn\'t process \`class { props: This }`'); } propTypes = props; return; } if (bodyNode && bodyNode.key.name === 'context' && bodyNode.typeAnnotation) { var _annotation = bodyNode.typeAnnotation.typeAnnotation; var context = getPropsForTypeAnnotation(_annotation); if (!context) { throw new TypeError('Couldn\'t process \`class { context: This }`'); } contextTypes = context; } }); // or Component<void, Props, Context> var secondSuperParam = getPropsTypeParam(path.node); if (secondSuperParam && secondSuperParam.type === 'GenericTypeAnnotation') { var typeAliasName = secondSuperParam.id.name; if (typeAliasName === 'Object') return; var props = internalTypes[typeAliasName] || importedTypes[typeAliasName] && importedTypes[typeAliasName].accessNode; if (!props) { (0, _util.$debug)("Couldn't find type \"".concat(typeAliasName, "\"")); return; } propTypes = props; } if (secondSuperParam && (secondSuperParam.type === 'ObjectTypeAnnotation' || secondSuperParam.type === 'IntersectionTypeAnnotation')) { propTypes = (0, _convertToPropTypes["default"])(secondSuperParam, importedTypes, internalTypes); } var thirdSuperParam = getContextTypeParam(path.node); if (thirdSuperParam && thirdSuperParam.type === 'GenericTypeAnnotation') { var _typeAliasName = thirdSuperParam.id.name; if (_typeAliasName === 'Object') return; var _props = internalTypes[_typeAliasName] || importedTypes[_typeAliasName] && importedTypes[_typeAliasName].accessNode; if (!_props) { throw new TypeError("Couldn't find type \"".concat(_typeAliasName, "\"")); } contextTypes = _props; } annotate(path, propTypes, contextTypes); }, FunctionExpression: function FunctionExpression(path) { if (suppress) return; return functionVisitor(path); }, FunctionDeclaration: function FunctionDeclaration(path) { if (suppress) return; return functionVisitor(path); }, ArrowFunctionExpression: function ArrowFunctionExpression(path) { if (suppress) return; return functionVisitor(path); }, // See issue: /** * Processes exported type aliases. * * This function also adds something to the AST directly, instead * of invoking annotate. * * @param path * @constructor */ ExportNamedDeclaration: function ExportNamedDeclaration(path) { if (suppress) return; var node = path.node; if (node.exportKind === 'type' && node.source && node.source.value) { var _iterator = _createForOfIteratorHelper(node.specifiers), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var spec = _step.value; var typeName = spec.local.name; getFromModule(path, { type: 'named', name: (0, _util.getExportNameForType)(typeName), location: node.source.value }); addExportTypeDecl(path, (0, _util.getExportNameForType)(typeName)); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return; } if (node.exportKind === 'type' && !node.source && !node.declaration) { var _iterator2 = _createForOfIteratorHelper(node.specifiers), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _spec = _step2.value; if (!t.isIdentifier(_spec.local)) continue; var imported = importedTypes[_spec.local.name]; if (!imported) continue; if (_spec.local.name !== _spec.exported.name) { // TODO: handle this properly continue; } addExportTypeDecl(path, (0, _util.getExportNameForType)(_spec.local.name), imported.accessNode); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return; } var declarationObject = null; if (!node.declaration) return; if (node.declaration.type === 'TypeAlias') { declarationObject = node.declaration.right; } if (node.declaration.type === 'OpaqueType') { declarationObject = node.declaration.impltype; } if (node.declaration.type === 'InterfaceDeclaration') { declarationObject = node.declaration.body; } if (!declarationObject) return; var name = node.declaration.id.name; var propTypes = convertNodeToPropTypes(declarationObject); internalTypes[name] = propTypes; var propTypesAst = (0, _makePropTypesAst.makePropTypesAstForExport)(propTypes); // create a variable for reuse var exportName = (0, _util.getExportNameForType)(name); exportedTypes[name] = exportName; var variableDeclarationAst = t.variableDeclaration('var', [t.variableDeclarator(t.identifier(exportName), wrapInDceCheck(propTypesAst))]); path.insertBefore(variableDeclarationAst); if (!omitRuntimeTypeExport) { if (path.node[SKIP]) return; addExportTypeDecl(path, exportName); } }, ImportDeclaration: function ImportDeclaration(path) { if (suppress) return; var node = path.node; if (/^@?\w/.test(node.source.value) && node.source.value !== 'react') return; // https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/62 // if (node.source.value[0] !== '.') { // return; // } node.specifiers.forEach(function (specifier) { if (node.importKind !== 'type' && specifier.importKind !== 'type') return; var typeName = specifier.local.name; var originalTypeName = specifier.type === 'ImportDefaultSpecifier' ? typeName : specifier.imported.name; // Store the name the type so we can use it later. We do // mark it as importedTypes because we do handle these // differently than internalTypes. // imported types are basically realized as imports; // because we can be somewhat sure that we generated // the proper exported propTypes in the imported file // Later, we will check importedTypes to determine if // we want to put this as a 'raw' type in our internal // representation importedTypes[typeName] = { localName: originalTypeName, exportName: (0, _util.getExportNameForType)(originalTypeName), accessNode: null }; // https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/129 if (node.source.value === 'react' && typeName === 'ComponentType') { var ptFunc = getFromPropTypesModule(path, 'func'); importedTypes[typeName].accessNode = ptFunc; return; } if (node.source.value === 'react' && typeName === 'Node') { var _ptFunc = getFromPropTypesModule(path, 'node'); importedTypes[typeName].accessNode = _ptFunc; return; } var accessNode = getFromModule(path, { type: 'named', name: (0, _util.getExportNameForType)(originalTypeName), local: (0, _util.getExportNameForType)(typeName), location: node.source.value }); importedTypes[typeName].accessNode = accessNode; }); } } }; }; function getPropsTypeParam(node) { if (!node) return null; if (!node.superTypeParameters) return null; var superTypes = node.superTypeParameters; if (superTypes.params.length === 2) { return superTypes.params[0]; } else if (superTypes.params.length === 3) { return superTypes.params[0]; } else if (superTypes.params.length === 1) { return superTypes.params[0]; } return null; } function getContextTypeParam(node) { if (!node) return null; if (!node.superTypeParameters) return null; var superTypes = node.superTypeParameters; if (superTypes.params.length === 3) { return superTypes.params[2]; } return null; }