1 | 'use strict'
|
2 |
|
3 | const la = require('lazy-ass')
|
4 | const is = require('check-more-types')
|
5 | const falafel = require('falafel')
|
6 | const crypto = require('crypto')
|
7 | const debug = require('debug')('snap-shot')
|
8 | const path = require('path')
|
9 |
|
10 | function isTestFunctionName (name) {
|
11 | return ['it', 'test'].includes(name)
|
12 | }
|
13 |
|
14 | function isTestFunction (node) {
|
15 | if (node.type === 'Identifier') {
|
16 | return isTestFunctionName(node.name)
|
17 | }
|
18 | if (node.type === 'MemberExpression') {
|
19 | return isTestFunctionName(node.object.name)
|
20 | }
|
21 | }
|
22 |
|
23 | function sha256 (string) {
|
24 | la(is.unemptyString(string), 'missing string to SHA', string)
|
25 | const hash = crypto.createHash('sha256')
|
26 | hash.update(string)
|
27 | return hash.digest('hex')
|
28 | }
|
29 |
|
30 | function snapshotIndex ({counters, specName}) {
|
31 | la(is.object(counters), 'expected counters', counters)
|
32 | la(is.unemptyString(specName), 'expected specName', specName)
|
33 | if (!(specName in counters)) {
|
34 | counters[specName] = 1
|
35 | } else {
|
36 | counters[specName] += 1
|
37 | }
|
38 | return counters[specName]
|
39 | }
|
40 |
|
41 | function shouldTranspile () {
|
42 | const exists = require('fs').existsSync
|
43 | if (!exists) {
|
44 | return false
|
45 | }
|
46 | return exists(path.join(process.cwd(), '.babelrc'))
|
47 | }
|
48 |
|
49 | function transpile (filename) {
|
50 | debug('transpiling %s', filename)
|
51 | const babel = require('babel-core')
|
52 | const {transformFileSync} = babel
|
53 |
|
54 | const opts = {
|
55 | sourceMaps: false,
|
56 | retainLines: true
|
57 | }
|
58 | const {code} = transformFileSync(filename, opts)
|
59 | return code
|
60 | }
|
61 |
|
62 | function findInSource ({source, file, line}) {
|
63 | la(is.string(source), 'could not get source from', file)
|
64 |
|
65 | let foundSpecName, specSource, startLine
|
66 | const options = {
|
67 | locations: true,
|
68 | sourceType: 'module'
|
69 | }
|
70 |
|
71 | falafel(source, options, node => {
|
72 | if (foundSpecName) {
|
73 |
|
74 | return
|
75 | }
|
76 |
|
77 | if (node.type === 'CallExpression') {
|
78 | if (isTestFunction(node.callee)) {
|
79 | if (node.loc.start.line < line &&
|
80 | node.loc.end.line > line) {
|
81 | debug('found test function around snapshot at line %d', line)
|
82 |
|
83 | if (node.arguments.length !== 2) {
|
84 | throw new Error('Cannot get test name for ' + node.source())
|
85 | }
|
86 |
|
87 | specSource = node.arguments[1].source()
|
88 | startLine = node.loc.start.line
|
89 |
|
90 |
|
91 |
|
92 | let specName
|
93 |
|
94 | const nameNode = node.arguments[0]
|
95 | if (nameNode.type === 'TemplateLiteral') {
|
96 | specName = nameNode.source().replace(/`/g, '')
|
97 | debug('template literal name "%s"', specName)
|
98 | } else if (nameNode.type === 'Literal') {
|
99 | specName = nameNode.value
|
100 | debug('regular string name "%s", specName')
|
101 | } else {
|
102 |
|
103 | }
|
104 |
|
105 | if (node.arguments.length === 2 &&
|
106 | node.arguments[1].type === 'FunctionExpression' &&
|
107 | node.arguments[1].id &&
|
108 | is.unemptyString(node.arguments[1].id.name)) {
|
109 | specName = node.arguments[1].id.name
|
110 | debug('callback function has a name "%s"', specName)
|
111 | }
|
112 |
|
113 | foundSpecName = specName
|
114 |
|
115 | if (!foundSpecName) {
|
116 | const hash = sha256(specSource)
|
117 | debug('using source hash %s for found spec in %s line %d',
|
118 | hash, file, line)
|
119 | foundSpecName = hash
|
120 | }
|
121 | }
|
122 | }
|
123 | }
|
124 | })
|
125 | return {
|
126 | specName: foundSpecName,
|
127 | specSource,
|
128 | startLine
|
129 | }
|
130 | }
|
131 |
|
132 | function getSpecFunction ({fs, file, line}) {
|
133 |
|
134 | la(is.unemptyString(file), 'missing file', file)
|
135 | la(is.number(line), 'missing line number', line)
|
136 |
|
137 | let found
|
138 | try {
|
139 | const source = fs.readFileSync(file, 'utf8')
|
140 | found = findInSource({source, file, line})
|
141 | } catch (e) {
|
142 | debug('problem finding without transpiling %s %d', file, line)
|
143 | }
|
144 |
|
145 | if (found) {
|
146 | return found
|
147 | }
|
148 |
|
149 |
|
150 | if (shouldTranspile()) {
|
151 | const source = transpile(file)
|
152 | found = findInSource({source, file, line})
|
153 | return found
|
154 | }
|
155 | }
|
156 |
|
157 |
|
158 |
|
159 | function strip (o) {
|
160 | return JSON.parse(JSON.stringify(o))
|
161 | }
|
162 |
|
163 | module.exports = {
|
164 | snapshotIndex,
|
165 | getSpecFunction,
|
166 | strip
|
167 | }
|