Added test to ensure that release notes are parsed properly Updated to latest dependencies to fix security vulnerability Updated GH Actions Workflow to run test before updating tag Updated README.md to reflect proper whatsnew documentation.
332 lines
9.2 KiB
JavaScript
332 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', {
|
|
value: true
|
|
});
|
|
exports.saveInlineSnapshots = void 0;
|
|
|
|
var _fs = _interopRequireDefault(require('fs'));
|
|
|
|
var _path = _interopRequireDefault(require('path'));
|
|
|
|
var _semver = _interopRequireDefault(require('semver'));
|
|
|
|
var _types = require('@babel/types');
|
|
|
|
var _utils = require('./utils');
|
|
|
|
function _interopRequireDefault(obj) {
|
|
return obj && obj.__esModule ? obj : {default: obj};
|
|
}
|
|
|
|
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
|
|
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
|
|
|
|
var jestWriteFile =
|
|
global[Symbol.for('jest-native-write-file')] || _fs.default.writeFileSync;
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(
|
|
Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
})
|
|
);
|
|
}
|
|
ownKeys.forEach(function(key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
|
|
|
|
var jestReadFile =
|
|
global[Symbol.for('jest-native-read-file')] || _fs.default.readFileSync;
|
|
|
|
const saveInlineSnapshots = (snapshots, prettier, babelTraverse) => {
|
|
if (!prettier) {
|
|
throw new Error(
|
|
`Jest: Inline Snapshots requires Prettier.\n` +
|
|
`Please ensure "prettier" is installed in your project.`
|
|
);
|
|
} // Custom parser API was added in 1.5.0
|
|
|
|
if (_semver.default.lt(prettier.version, '1.5.0')) {
|
|
throw new Error(
|
|
`Jest: Inline Snapshots require prettier>=1.5.0.\n` +
|
|
`Please upgrade "prettier".`
|
|
);
|
|
}
|
|
|
|
const snapshotsByFile = groupSnapshotsByFile(snapshots);
|
|
|
|
var _arr = Object.keys(snapshotsByFile);
|
|
|
|
for (var _i = 0; _i < _arr.length; _i++) {
|
|
const sourceFilePath = _arr[_i];
|
|
saveSnapshotsForFile(
|
|
snapshotsByFile[sourceFilePath],
|
|
sourceFilePath,
|
|
prettier,
|
|
babelTraverse
|
|
);
|
|
}
|
|
};
|
|
|
|
exports.saveInlineSnapshots = saveInlineSnapshots;
|
|
|
|
const saveSnapshotsForFile = (
|
|
snapshots,
|
|
sourceFilePath,
|
|
prettier,
|
|
babelTraverse
|
|
) => {
|
|
const sourceFile = jestReadFile(sourceFilePath, 'utf8'); // Resolve project configuration.
|
|
// For older versions of Prettier, do not load configuration.
|
|
|
|
const config = prettier.resolveConfig
|
|
? prettier.resolveConfig.sync(sourceFilePath, {
|
|
editorconfig: true
|
|
})
|
|
: null; // Detect the parser for the test file.
|
|
// For older versions of Prettier, fallback to a simple parser detection.
|
|
|
|
const inferredParser = prettier.getFileInfo
|
|
? prettier.getFileInfo.sync(sourceFilePath).inferredParser
|
|
: (config && config.parser) || simpleDetectParser(sourceFilePath); // Insert snapshots using the custom parser API. After insertion, the code is
|
|
// formatted, except snapshot indentation. Snapshots cannot be formatted until
|
|
// after the initial format because we don't know where the call expression
|
|
// will be placed (specifically its indentation).
|
|
|
|
const newSourceFile = prettier.format(
|
|
sourceFile,
|
|
_objectSpread({}, config, {
|
|
filepath: sourceFilePath,
|
|
parser: createInsertionParser(snapshots, inferredParser, babelTraverse)
|
|
})
|
|
); // Format the snapshots using the custom parser API.
|
|
|
|
const formattedNewSourceFile = prettier.format(
|
|
newSourceFile,
|
|
_objectSpread({}, config, {
|
|
filepath: sourceFilePath,
|
|
parser: createFormattingParser(inferredParser, babelTraverse)
|
|
})
|
|
);
|
|
|
|
if (formattedNewSourceFile !== sourceFile) {
|
|
jestWriteFile(sourceFilePath, formattedNewSourceFile);
|
|
}
|
|
};
|
|
|
|
const groupSnapshotsBy = createKey => snapshots =>
|
|
snapshots.reduce((object, inlineSnapshot) => {
|
|
const key = createKey(inlineSnapshot);
|
|
return _objectSpread({}, object, {
|
|
[key]: (object[key] || []).concat(inlineSnapshot)
|
|
});
|
|
}, {});
|
|
|
|
const groupSnapshotsByFrame = groupSnapshotsBy(({frame: {line, column}}) =>
|
|
typeof line === 'number' && typeof column === 'number'
|
|
? `${line}:${column - 1}`
|
|
: ''
|
|
);
|
|
const groupSnapshotsByFile = groupSnapshotsBy(({frame: {file}}) => file);
|
|
|
|
const indent = (snapshot, numIndents, indentation) => {
|
|
const lines = snapshot.split('\n'); // Prevent re-identation of inline snapshots.
|
|
|
|
if (
|
|
lines.length >= 2 &&
|
|
lines[1].startsWith(indentation.repeat(numIndents + 1))
|
|
) {
|
|
return snapshot;
|
|
}
|
|
|
|
return lines
|
|
.map((line, index) => {
|
|
if (index === 0) {
|
|
// First line is either a 1-line snapshot or a blank line.
|
|
return line;
|
|
} else if (index !== lines.length - 1) {
|
|
// Do not indent empty lines.
|
|
if (line === '') {
|
|
return line;
|
|
} // Not last line, indent one level deeper than expect call.
|
|
|
|
return indentation.repeat(numIndents + 1) + line;
|
|
} else {
|
|
// The last line should be placed on the same level as the expect call.
|
|
return indentation.repeat(numIndents) + line;
|
|
}
|
|
})
|
|
.join('\n');
|
|
};
|
|
|
|
const getAst = (parsers, inferredParser, text) => {
|
|
// Flow uses a 'Program' parent node, babel expects a 'File'.
|
|
let ast = parsers[inferredParser](text);
|
|
|
|
if (ast.type !== 'File') {
|
|
ast = (0, _types.file)(ast, ast.comments, ast.tokens);
|
|
delete ast.program.comments;
|
|
}
|
|
|
|
return ast;
|
|
}; // This parser inserts snapshots into the AST.
|
|
|
|
const createInsertionParser = (snapshots, inferredParser, babelTraverse) => (
|
|
text,
|
|
parsers,
|
|
options
|
|
) => {
|
|
// Workaround for https://github.com/prettier/prettier/issues/3150
|
|
options.parser = inferredParser;
|
|
const groupedSnapshots = groupSnapshotsByFrame(snapshots);
|
|
const remainingSnapshots = new Set(snapshots.map(({snapshot}) => snapshot));
|
|
const ast = getAst(parsers, inferredParser, text);
|
|
babelTraverse(ast, {
|
|
CallExpression({node: {arguments: args, callee}}) {
|
|
if (
|
|
callee.type !== 'MemberExpression' ||
|
|
callee.property.type !== 'Identifier'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const _callee$property$loc$ = callee.property.loc.start,
|
|
line = _callee$property$loc$.line,
|
|
column = _callee$property$loc$.column;
|
|
const snapshotsForFrame = groupedSnapshots[`${line}:${column}`];
|
|
|
|
if (!snapshotsForFrame) {
|
|
return;
|
|
}
|
|
|
|
if (snapshotsForFrame.length > 1) {
|
|
throw new Error(
|
|
'Jest: Multiple inline snapshots for the same call are not supported.'
|
|
);
|
|
}
|
|
|
|
const snapshotIndex = args.findIndex(
|
|
({type}) => type === 'TemplateLiteral'
|
|
);
|
|
const values = snapshotsForFrame.map(({snapshot}) => {
|
|
remainingSnapshots.delete(snapshot);
|
|
return (0, _types.templateLiteral)(
|
|
[
|
|
(0, _types.templateElement)({
|
|
raw: (0, _utils.escapeBacktickString)(snapshot)
|
|
})
|
|
],
|
|
[]
|
|
);
|
|
});
|
|
const replacementNode = values[0];
|
|
|
|
if (snapshotIndex > -1) {
|
|
args[snapshotIndex] = replacementNode;
|
|
} else {
|
|
args.push(replacementNode);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (remainingSnapshots.size) {
|
|
throw new Error(`Jest: Couldn't locate all inline snapshots.`);
|
|
}
|
|
|
|
return ast;
|
|
}; // This parser formats snapshots to the correct indentation.
|
|
|
|
const createFormattingParser = (inferredParser, babelTraverse) => (
|
|
text,
|
|
parsers,
|
|
options
|
|
) => {
|
|
// Workaround for https://github.com/prettier/prettier/issues/3150
|
|
options.parser = inferredParser;
|
|
const ast = getAst(parsers, inferredParser, text);
|
|
babelTraverse(ast, {
|
|
CallExpression({node: {arguments: args, callee}}) {
|
|
if (
|
|
callee.type !== 'MemberExpression' ||
|
|
callee.property.type !== 'Identifier' ||
|
|
callee.property.name !== 'toMatchInlineSnapshot' ||
|
|
!callee.loc ||
|
|
callee.computed
|
|
) {
|
|
return;
|
|
}
|
|
|
|
let snapshotIndex;
|
|
let snapshot;
|
|
|
|
for (let i = 0; i < args.length; i++) {
|
|
const node = args[i];
|
|
|
|
if (node.type === 'TemplateLiteral') {
|
|
snapshotIndex = i;
|
|
snapshot = node.quasis[0].value.raw;
|
|
}
|
|
}
|
|
|
|
if (snapshot === undefined || snapshotIndex === undefined) {
|
|
return;
|
|
}
|
|
|
|
const useSpaces = !options.useTabs;
|
|
snapshot = indent(
|
|
snapshot,
|
|
Math.ceil(
|
|
useSpaces
|
|
? callee.loc.start.column / options.tabWidth
|
|
: callee.loc.start.column / 2 // Each tab is 2 characters.
|
|
),
|
|
useSpaces ? ' '.repeat(options.tabWidth) : '\t'
|
|
);
|
|
const replacementNode = (0, _types.templateLiteral)(
|
|
[
|
|
(0, _types.templateElement)({
|
|
raw: snapshot
|
|
})
|
|
],
|
|
[]
|
|
);
|
|
args[snapshotIndex] = replacementNode;
|
|
}
|
|
});
|
|
return ast;
|
|
};
|
|
|
|
const simpleDetectParser = filePath => {
|
|
const extname = _path.default.extname(filePath);
|
|
|
|
if (/tsx?$/.test(extname)) {
|
|
return 'typescript';
|
|
}
|
|
|
|
return 'babylon';
|
|
};
|