@typed/test
Version:
Testing made simple.
176 lines • 7.16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const typescript_1 = require("typescript");
const flatten_1 = require("../common/flatten");
const dependencies_1 = require("../typescript/dependencies");
const traverseNode_1 = require("../typescript/traverseNode");
function parseTestMetadata(sourceFiles,
// tslint:disable-next-line:ban-types
typedTestSymbol, typeChecker) {
const testNodes = findAllTestNodes(sourceFiles, typedTestSymbol, typeChecker);
const exportedTestIdentifiers = findExportedTestIndentifiers(testNodes);
const exportedTestNames = exportedTestIdentifiers.map(x => x.exported);
const testDeclarationNodes = testNodes.filter(isVariableDeclarationNodeSource(exportedTestNames));
return testDeclarationNodes.map(findTestMetadataFromNodeSource(exportedTestIdentifiers, testNodes));
}
exports.parseTestMetadata = parseTestMetadata;
function isVariableDeclarationNodeSource(exportNames) {
return (x) => typescript_1.isVariableDeclaration(x.node) && exportNames.includes(findIdentifingName(x.node));
}
function findTestMetadataFromNodeSource(testIdentifiers, testNodes) {
return (nodeSource) => {
const { node, sourceFile } = nodeSource;
const statement = node.parent.parent;
const declarationName = findVariableDeclarationName(node);
const exportNames = testIdentifiers
.filter(x => x.sourceFile.fileName === sourceFile.fileName && x.local === declarationName)
.map(x => x.exported);
const subTestNodes = testNodes.filter(findSubTests(nodeSource));
const subTests = subTestNodes
.map(findNodeMetadata)
.sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
const meta = Object.assign(Object.assign({}, findNodeMetadata({ node: statement, sourceFile })), { exportNames, filePath: sourceFile.fileName, additionalTests: [] });
return findAdditional(meta, subTests);
};
}
function findAdditional(metadata, nodes) {
const possibleTests = nodes
.filter(x => isContainedBy(metadata, x))
.sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
if (possibleTests.length === 0) {
return metadata;
}
const testsWithAddtional = possibleTests
.map((x, i) => findAdditional(x, possibleTests.slice(i)))
.filter(x => x.additionalTests.length > 0);
const others = possibleTests
.map((x, i) => findAdditional(x, possibleTests.slice(i)))
.filter(x => !testsWithAddtional.some(y => isContainedBy(y, x)));
const testsToUse = [
...others,
...testsWithAddtional.filter(x => !testsWithAddtional.some(y => y === x || isContainedBy(y, x))),
];
return Object.assign(Object.assign({}, metadata), { additionalTests: testsToUse.sort(({ position: a }, { position: b }) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)) });
}
function isContainedBy(parent, child) {
return parent.position[0] < child.position[0] && parent.position[1] > child.position[1];
}
function findSubTests({ node, sourceFile }) {
return (x) => x.sourceFile === sourceFile &&
x.node !== node &&
x.node.getStart() > node.getStart() &&
x.node.getEnd() < node.getEnd();
}
function findNodeMetadata({ node, sourceFile }) {
const subPosition = [node.getStart(), node.getEnd()];
const subLine = sourceFile.text.slice(0, subPosition[0]).split(/\n/g).length;
const subText = node.getText();
const subLines = subText.split(/\n/g).length;
return {
line: subLine,
lines: subLines,
position: subPosition,
text: subText,
additionalTests: [],
};
}
function findAllTestNodes(sourceFiles, symbol, typeChecker) {
return flatten_1.flatten(sourceFiles.map(sourceFile => {
if (sourceFile.isDeclarationFile || !dependencies_1.hasExports(sourceFile)) {
return [];
}
return traverseNode_1.traverseNode(findTestNodes(symbol, typeChecker), [], sourceFile).map(node => ({
node,
sourceFile,
}));
}));
}
function findExportedTestIndentifiers(nodes) {
return nodes
.filter(x => isExportedVariableDeclaration(x.node) || isExportedIdentifier(x.node))
.map(x => typescript_1.isVariableDeclaration(x.node)
? {
exported: findVariableDeclarationName(x.node),
local: findVariableDeclarationName(x.node),
sourceFile: x.sourceFile,
}
: Object.assign(Object.assign({}, findIdentifierName(x.node)), { sourceFile: x.sourceFile }));
}
function isExportedVariableDeclaration(x) {
return ((typescript_1.isVariableDeclaration(x) &&
x.parent &&
typescript_1.isVariableDeclarationList(x.parent) &&
x.parent.parent &&
typescript_1.isVariableStatement(x.parent.parent) &&
x.parent.parent.modifiers &&
x.parent.parent.modifiers.some(m => m.kind === typescript_1.SyntaxKind.ExportKeyword)) ||
false);
}
function isExportedIdentifier(x) {
if (typescript_1.isIdentifier(x) && x.parent) {
if (typescript_1.isExportAssignment(x.parent) || typescript_1.isExportSpecifier(x.parent)) {
return true;
}
}
return false;
}
function findTestNodes(testSymbol, typeChecker) {
return (testNodes, node) => {
const types = tryGetTypes(node, typeChecker);
if (!types) {
return testNodes;
}
return types.map(tryGetSymbol).some(x => x === testSymbol) ? testNodes.concat(node) : testNodes;
};
}
function findIdentifingName(x) {
return typescript_1.isVariableDeclaration(x) ? findVariableDeclarationName(x) : findIdentifierName(x).exported;
}
function findIdentifierName(x) {
if (x.parent && typescript_1.isExportAssignment(x.parent) && x.parent.getText().includes('export default ')) {
return { exported: 'default', local: x.text };
}
return { exported: x.text, local: x.text };
}
function findVariableDeclarationName(x) {
const nodes = dependencies_1.getNodes(x);
for (const node of nodes) {
if (typescript_1.isIdentifier(node)) {
return node.text;
}
}
if (typescript_1.isIdentifier(x.name)) {
return x.name.text;
}
throw new Error('Unabled to find test name');
}
function tryGetTypes(node, typeChecker) {
try {
const type = typeChecker.getWidenedType(typeChecker.getTypeAtLocation(node));
const types = [type];
const baseTypes = type.getBaseTypes();
if (baseTypes) {
types.push(...baseTypes);
}
if (typescript_1.isCallLikeExpression(node)) {
const returnType = typeChecker.getReturnTypeOfSignature(typeChecker.getResolvedSignature(node));
types.push(returnType);
}
return types;
}
catch (_a) {
return undefined;
}
}
function tryGetSymbol(type) {
if (!type) {
return;
}
try {
return type.getSymbol();
}
catch (_a) {
return undefined;
}
}
//# sourceMappingURL=parseTestMetadata.js.map