mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
592 lines
20 KiB
JavaScript
592 lines
20 KiB
JavaScript
/*
|
|
Copyright 2020 the JSDoc Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
describe('@jsdoc/parse/lib/ast-node', () => {
|
|
const astNode = require('../../../lib/ast-node');
|
|
const babelParser = require('@babel/parser');
|
|
const { parserOptions } = require('../../../lib/ast-builder');
|
|
const { Syntax } = require('../../../lib/syntax');
|
|
|
|
function parse(str) {
|
|
return babelParser.parse(str, parserOptions).program.body[0];
|
|
}
|
|
|
|
// create the AST nodes we'll be testing
|
|
const arrayExpression = parse('[,]').expression;
|
|
const arrowFunctionExpression = parse('var foo = () => {};').declarations[0].init;
|
|
const assignmentExpression = parse('foo = 1;').expression;
|
|
const binaryExpression = parse('foo & foo;').expression;
|
|
const experimentalObjectRestSpread = parse('var one = {...two, three: 4};').declarations[0].init;
|
|
const functionDeclaration1 = parse('function foo() {}');
|
|
const functionDeclaration2 = parse('function foo(bar) {}');
|
|
const functionDeclaration3 = parse('function foo(bar, baz, qux) {}');
|
|
const functionDeclaration4 = parse('function foo(...bar) {}');
|
|
const functionExpression1 = parse('var foo = function() {};').declarations[0].init;
|
|
const functionExpression2 = parse('var foo = function(bar) {};').declarations[0].init;
|
|
const identifier = parse('foo;').expression;
|
|
const literal = parse('1;').expression;
|
|
const memberExpression = parse('foo.bar;').expression;
|
|
const memberExpressionComputed1 = parse('foo["bar"];').expression;
|
|
const memberExpressionComputed2 = parse("foo['bar'];").expression;
|
|
const methodDefinition1 = parse('class Foo { bar() {} }').body.body[0];
|
|
const methodDefinition2 = parse('var foo = () => class { bar() {} };').declarations[0].init.body
|
|
.body[0];
|
|
const propertyGet = parse('var foo = { get bar() {} };').declarations[0].init.properties[0];
|
|
const propertyInit = parse('var foo = { bar: {} };').declarations[0].init.properties[0];
|
|
const propertySet = parse('var foo = { set bar(a) {} };').declarations[0].init.properties[0];
|
|
const thisExpression = parse('this;').expression;
|
|
const unaryExpression1 = parse('+1;').expression;
|
|
const unaryExpression2 = parse('+foo;').expression;
|
|
const variableDeclarator1 = parse('var foo = 1;').declarations[0];
|
|
const variableDeclarator2 = parse('var foo;').declarations[0];
|
|
|
|
it('should exist', () => {
|
|
expect(astNode).toBeObject();
|
|
});
|
|
|
|
it('should export an addNodeProperties method', () => {
|
|
expect(astNode.addNodeProperties).toBeFunction();
|
|
});
|
|
|
|
it('should export a getInfo method', () => {
|
|
expect(astNode.getInfo).toBeFunction();
|
|
});
|
|
|
|
it('should export a getParamNames method', () => {
|
|
expect(astNode.getParamNames).toBeFunction();
|
|
});
|
|
|
|
it('should export an isAccessor method', () => {
|
|
expect(astNode.isAccessor).toBeFunction();
|
|
});
|
|
|
|
it('should export an isAssignment method', () => {
|
|
expect(astNode.isAssignment).toBeFunction();
|
|
});
|
|
|
|
it('should export an isFunction method', () => {
|
|
expect(astNode.isFunction).toBeFunction();
|
|
});
|
|
|
|
it('should export an isScope method', () => {
|
|
expect(astNode.isScope).toBeFunction();
|
|
});
|
|
|
|
it('should export a nodeToString method', () => {
|
|
expect(astNode.nodeToString).toBeFunction();
|
|
});
|
|
|
|
it('should export a nodeToValue method', () => {
|
|
expect(astNode.nodeToValue).toBeFunction();
|
|
});
|
|
|
|
describe('addNodeProperties', () => {
|
|
it('should return null for undefined input', () => {
|
|
expect(astNode.addNodeProperties()).toBe(null);
|
|
});
|
|
|
|
it('should return null if the input is not an object', () => {
|
|
expect(astNode.addNodeProperties('foo')).toBe(null);
|
|
});
|
|
|
|
it('should preserve existing properties that are not "node properties"', () => {
|
|
const node = astNode.addNodeProperties({ foo: 1 });
|
|
|
|
expect(node).toBeObject();
|
|
expect(node.foo).toBe(1);
|
|
});
|
|
|
|
it('should add a nodeId if necessary', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, 'nodeId');
|
|
|
|
expect(descriptor).toBeObject();
|
|
expect(descriptor.value).toBeString();
|
|
expect(descriptor.enumerable).toBeTrue();
|
|
});
|
|
|
|
it('should not overwrite an existing nodeId', () => {
|
|
const nodeId = 'foo';
|
|
const node = astNode.addNodeProperties({ nodeId: nodeId });
|
|
|
|
expect(node.nodeId).toBe(nodeId);
|
|
});
|
|
|
|
it('should add a non-enumerable, writable parent if necessary', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, 'parent');
|
|
|
|
expect(descriptor).toBeDefined();
|
|
expect(descriptor.value).toBeUndefined();
|
|
expect(descriptor.enumerable).toBeFalse();
|
|
expect(descriptor.writable).toBeTrue();
|
|
});
|
|
|
|
it('should not overwrite an existing parent', () => {
|
|
const parent = {};
|
|
const node = astNode.addNodeProperties({ parent: parent });
|
|
|
|
expect(node.parent).toBe(parent);
|
|
});
|
|
|
|
it('should not overwrite a null parent', () => {
|
|
const node = astNode.addNodeProperties({ parent: null });
|
|
|
|
expect(node.parent).toBeNull();
|
|
});
|
|
|
|
it('should add an enumerable parentId', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, 'parentId');
|
|
|
|
expect(descriptor).toBeObject();
|
|
expect(descriptor.enumerable).toBeTrue();
|
|
});
|
|
|
|
it('should provide a null parentId for nodes with no parent', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
|
|
expect(node.parentId).toBeNull();
|
|
});
|
|
|
|
it('should provide a non-null parentId for nodes with a parent', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const parent = astNode.addNodeProperties({});
|
|
|
|
node.parent = parent;
|
|
|
|
expect(node.parentId).toBe(parent.nodeId);
|
|
});
|
|
|
|
it('should add a non-enumerable, writable enclosingScope if necessary', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, 'enclosingScope');
|
|
|
|
expect(descriptor).toBeObject();
|
|
expect(descriptor.value).toBeUndefined();
|
|
expect(descriptor.enumerable).toBeFalse();
|
|
expect(descriptor.writable).toBeTrue();
|
|
});
|
|
|
|
it('should not overwrite an existing enclosingScope', () => {
|
|
const enclosingScope = {};
|
|
const node = astNode.addNodeProperties({ enclosingScope: enclosingScope });
|
|
|
|
expect(node.enclosingScope).toBe(enclosingScope);
|
|
});
|
|
|
|
it('should not overwrite a null enclosingScope', () => {
|
|
const node = astNode.addNodeProperties({ enclosingScope: null });
|
|
|
|
expect(node.enclosingScope).toBeNull();
|
|
});
|
|
|
|
it('should add an enumerable enclosingScopeId', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, 'enclosingScopeId');
|
|
|
|
expect(descriptor).toBeObject();
|
|
expect(descriptor.enumerable).toBeTrue();
|
|
});
|
|
|
|
it('should provide a null enclosingScopeId for nodes with no enclosing scope', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
|
|
expect(node.enclosingScopeId).toBeNull();
|
|
});
|
|
|
|
it('should provide a non-null enclosingScopeId for nodes with an enclosing scope', () => {
|
|
const node = astNode.addNodeProperties({});
|
|
const enclosingScope = astNode.addNodeProperties({});
|
|
|
|
node.enclosingScope = enclosingScope;
|
|
|
|
expect(node.enclosingScopeId).toBe(enclosingScope.nodeId);
|
|
});
|
|
});
|
|
|
|
describe('getInfo', () => {
|
|
it('should throw an error for undefined input', () => {
|
|
function noNode() {
|
|
astNode.getInfo();
|
|
}
|
|
|
|
expect(noNode).toThrow();
|
|
});
|
|
|
|
it('should return the correct info for an AssignmentExpression', () => {
|
|
const info = astNode.getInfo(assignmentExpression);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.Literal);
|
|
expect(info.node.value).toBe(1);
|
|
|
|
expect(info.name).toBe('foo');
|
|
expect(info.type).toBe(Syntax.Literal);
|
|
expect(info.value).toBe(1);
|
|
});
|
|
|
|
it('should return the correct info for a FunctionDeclaration', () => {
|
|
const info = astNode.getInfo(functionDeclaration2);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.FunctionDeclaration);
|
|
|
|
expect(info.name).toBe('foo');
|
|
expect(info.type).toBe(Syntax.FunctionDeclaration);
|
|
expect(info.value).toBeUndefined();
|
|
|
|
expect(info.paramnames).toBeArrayOfSize(1);
|
|
expect(info.paramnames[0]).toBe('bar');
|
|
});
|
|
|
|
it('should return the correct info for a FunctionExpression', () => {
|
|
const info = astNode.getInfo(functionExpression2);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.FunctionExpression);
|
|
|
|
expect(info.name).toBe('');
|
|
expect(info.type).toBe(Syntax.FunctionExpression);
|
|
expect(info.value).toBeUndefined();
|
|
|
|
expect(info.paramnames).toBeArrayOfSize(1);
|
|
expect(info.paramnames[0]).toBe('bar');
|
|
});
|
|
|
|
it('should return the correct info for a MemberExpression', () => {
|
|
const info = astNode.getInfo(memberExpression);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.MemberExpression);
|
|
|
|
expect(info.name).toBe('foo.bar');
|
|
expect(info.type).toBe(Syntax.MemberExpression);
|
|
});
|
|
|
|
it('should return the correct info for a computed MemberExpression', () => {
|
|
const info = astNode.getInfo(memberExpressionComputed1);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.MemberExpression);
|
|
|
|
expect(info.name).toBe('foo["bar"]');
|
|
expect(info.type).toBe(Syntax.MemberExpression);
|
|
});
|
|
|
|
it('should return the correct info for a Property initializer', () => {
|
|
const info = astNode.getInfo(propertyInit);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.ObjectExpression);
|
|
|
|
expect(info.name).toBe('bar');
|
|
expect(info.type).toBe(Syntax.ObjectExpression);
|
|
});
|
|
|
|
it('should return the correct info for a Property setter', () => {
|
|
const info = astNode.getInfo(propertySet);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.FunctionExpression);
|
|
|
|
expect(info.name).toBe('bar');
|
|
expect(info.type).toBeUndefined();
|
|
expect(info.value).toBeUndefined();
|
|
|
|
expect(info.paramnames).toBeArrayOfSize(1);
|
|
expect(info.paramnames[0]).toBe('a');
|
|
});
|
|
|
|
it('should return the correct info for a VariableDeclarator with a value', () => {
|
|
const info = astNode.getInfo(variableDeclarator1);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.Literal);
|
|
|
|
expect(info.name).toBe('foo');
|
|
expect(info.type).toBe(Syntax.Literal);
|
|
expect(info.value).toBe(1);
|
|
});
|
|
|
|
it('should return the correct info for a VariableDeclarator with no value', () => {
|
|
const info = astNode.getInfo(variableDeclarator2);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBeObject();
|
|
expect(info.node.type).toBe(Syntax.Identifier);
|
|
|
|
expect(info.name).toBe('foo');
|
|
expect(info.type).toBeUndefined();
|
|
expect(info.value).toBeUndefined();
|
|
});
|
|
|
|
it('should return the correct info for other node types', () => {
|
|
const info = astNode.getInfo(binaryExpression);
|
|
|
|
expect(info).toBeObject();
|
|
|
|
expect(info.node).toBe(binaryExpression);
|
|
expect(info.type).toBe(Syntax.BinaryExpression);
|
|
});
|
|
});
|
|
|
|
describe('getParamNames', () => {
|
|
it('should return an empty array for undefined input', () => {
|
|
const params = astNode.getParamNames();
|
|
|
|
expect(params).toBeEmptyArray();
|
|
});
|
|
|
|
it('should return an empty array if the input has no params property', () => {
|
|
const params = astNode.getParamNames({});
|
|
|
|
expect(params).toBeEmptyArray();
|
|
});
|
|
|
|
it('should return an empty array if the input has no params', () => {
|
|
const params = astNode.getParamNames(functionDeclaration1);
|
|
|
|
expect(params).toBeEmptyArray();
|
|
});
|
|
|
|
it('should return a single-item array if the input has a single param', () => {
|
|
const params = astNode.getParamNames(functionDeclaration2);
|
|
|
|
expect(params).toEqual(['bar']);
|
|
});
|
|
|
|
it('should return a multi-item array if the input has multiple params', () => {
|
|
const params = astNode.getParamNames(functionDeclaration3);
|
|
|
|
expect(params).toEqual(['bar', 'baz', 'qux']);
|
|
});
|
|
|
|
it('should include rest parameters', () => {
|
|
const params = astNode.getParamNames(functionDeclaration4);
|
|
|
|
expect(params).toEqual(['bar']);
|
|
});
|
|
});
|
|
|
|
describe('isAccessor', () => {
|
|
it('should return false for undefined values', () => {
|
|
expect(astNode.isAccessor()).toBeFalse();
|
|
});
|
|
|
|
it('should return false if the parameter is not an object', () => {
|
|
expect(astNode.isAccessor('foo')).toBeFalse();
|
|
});
|
|
|
|
it('should return false for non-Property nodes', () => {
|
|
expect(astNode.isAccessor(binaryExpression)).toBeFalse();
|
|
});
|
|
|
|
it('should return false for Property nodes whose kind is "init"', () => {
|
|
expect(astNode.isAccessor(propertyInit)).toBeFalse();
|
|
});
|
|
|
|
it('should return true for Property nodes whose kind is "get"', () => {
|
|
expect(astNode.isAccessor(propertyGet)).toBeTrue();
|
|
});
|
|
|
|
it('should return true for Property nodes whose kind is "set"', () => {
|
|
expect(astNode.isAccessor(propertySet)).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('isAssignment', () => {
|
|
it('should return false for undefined values', () => {
|
|
expect(astNode.isAssignment()).toBeFalse();
|
|
});
|
|
|
|
it('should return false if the parameter is not an object', () => {
|
|
expect(astNode.isAssignment('foo')).toBeFalse();
|
|
});
|
|
|
|
it('should return false for nodes that are not assignments', () => {
|
|
expect(astNode.isAssignment(binaryExpression)).toBeFalse();
|
|
});
|
|
|
|
it('should return true for AssignmentExpression nodes', () => {
|
|
expect(astNode.isAssignment(assignmentExpression)).toBeTrue();
|
|
});
|
|
|
|
it('should return true for VariableDeclarator nodes', () => {
|
|
expect(astNode.isAssignment(variableDeclarator1)).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('isFunction', () => {
|
|
it('should recognize function declarations as functions', () => {
|
|
expect(astNode.isFunction(functionDeclaration1)).toBeTrue();
|
|
});
|
|
|
|
it('should recognize function expressions as functions', () => {
|
|
expect(astNode.isFunction(functionExpression1)).toBeTrue();
|
|
});
|
|
|
|
it('should recognize method definitions as functions', () => {
|
|
expect(astNode.isFunction(methodDefinition1)).toBeTrue();
|
|
});
|
|
|
|
it('should recognize arrow function expressions as functions', () => {
|
|
expect(astNode.isFunction(arrowFunctionExpression)).toBeTrue();
|
|
});
|
|
|
|
it('should recognize non-functions', () => {
|
|
expect(astNode.isFunction(arrayExpression)).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isScope', () => {
|
|
it('should return false for undefined values', () => {
|
|
expect(astNode.isScope()).toBeFalse();
|
|
});
|
|
|
|
it('should return false if the parameter is not an object', () => {
|
|
expect(astNode.isScope('foo')).toBeFalse();
|
|
});
|
|
|
|
it('should return true for CatchClause nodes', () => {
|
|
expect(astNode.isScope({ type: Syntax.CatchClause })).toBeTrue();
|
|
});
|
|
|
|
it('should return true for FunctionDeclaration nodes', () => {
|
|
expect(astNode.isScope({ type: Syntax.FunctionDeclaration })).toBeTrue();
|
|
});
|
|
|
|
it('should return true for FunctionExpression nodes', () => {
|
|
expect(astNode.isScope({ type: Syntax.FunctionExpression })).toBeTrue();
|
|
});
|
|
|
|
it('should return false for other nodes', () => {
|
|
expect(astNode.isScope({ type: Syntax.NameExpression })).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('nodeToString', () => {
|
|
it('should be an alias to nodeToValue', () => {
|
|
expect(astNode.nodeToString).toBe(astNode.nodeToValue);
|
|
});
|
|
});
|
|
|
|
describe('nodeToValue', () => {
|
|
it('should return `[null]` for the sparse array `[,]`', () => {
|
|
expect(astNode.nodeToValue(arrayExpression)).toBe('[null]');
|
|
});
|
|
|
|
it('should return the variable name for assignment expressions', () => {
|
|
expect(astNode.nodeToValue(assignmentExpression)).toBe('foo');
|
|
});
|
|
|
|
it('should return the function name for function declarations', () => {
|
|
expect(astNode.nodeToValue(functionDeclaration1)).toBe('foo');
|
|
});
|
|
|
|
it('should return undefined for anonymous function expressions', () => {
|
|
expect(astNode.nodeToValue(functionExpression1)).toBeUndefined();
|
|
});
|
|
|
|
it('should return the identifier name for identifiers', () => {
|
|
expect(astNode.nodeToValue(identifier)).toBe('foo');
|
|
});
|
|
|
|
it('should return the literal value for literals', () => {
|
|
expect(astNode.nodeToValue(literal)).toBe(1);
|
|
});
|
|
|
|
it('should return the object and property for noncomputed member expressions', () => {
|
|
expect(astNode.nodeToValue(memberExpression)).toBe('foo.bar');
|
|
});
|
|
|
|
it(
|
|
'should return the object and property, with a computed property that uses the same ' +
|
|
'quote character as the original source, for computed member expressions',
|
|
() => {
|
|
expect(astNode.nodeToValue(memberExpressionComputed1)).toBe('foo["bar"]');
|
|
expect(astNode.nodeToValue(memberExpressionComputed2)).toBe("foo['bar']");
|
|
}
|
|
);
|
|
|
|
// TODO: we can't test this here because JSDoc, not Babylon, adds the `parent` property to
|
|
// nodes. also, we currently return an empty string instead of `<anonymous>` in this case;
|
|
// see `module:@jsdoc/parse.astNode.nodeToValue` and the comment on
|
|
// `Syntax.MethodDefinition` for details
|
|
xit(
|
|
'should return `<anonymous>` for method definitions inside classes that were ' +
|
|
'returned by an arrow function expression',
|
|
() => {
|
|
expect(astNode.nodeToValue(methodDefinition2)).toBe('<anonymous>');
|
|
}
|
|
);
|
|
|
|
it('should return "this" for this expressions', () => {
|
|
expect(astNode.nodeToValue(thisExpression)).toBe('this');
|
|
});
|
|
|
|
it('should return the operator and nodeToValue value for prefix unary expressions', () => {
|
|
expect(astNode.nodeToValue(unaryExpression1)).toBe('+1');
|
|
expect(astNode.nodeToValue(unaryExpression2)).toBe('+foo');
|
|
});
|
|
|
|
it('should throw an error for postfix unary expressions', () => {
|
|
function postfixNodeToValue() {
|
|
// there's no valid source representation for this one, so we fake it
|
|
const unaryExpressionPostfix = (() => {
|
|
const node = parse('+1;').body[0].expression;
|
|
|
|
node.prefix = false;
|
|
|
|
return node;
|
|
})();
|
|
|
|
return astNode.nodeToValue(unaryExpressionPostfix);
|
|
}
|
|
|
|
expect(postfixNodeToValue).toThrow();
|
|
});
|
|
|
|
it('should return the variable name for variable declarators', () => {
|
|
expect(astNode.nodeToValue(variableDeclarator1)).toBe('foo');
|
|
});
|
|
|
|
it('should return an empty string for all other nodes', () => {
|
|
expect(astNode.nodeToValue(binaryExpression)).toBe('');
|
|
});
|
|
|
|
it('should understand and ignore ExperimentalSpreadProperty', () => {
|
|
expect(astNode.nodeToValue(experimentalObjectRestSpread)).toBe('{"three":4}');
|
|
});
|
|
});
|
|
});
|