diff --git a/package.json b/package.json index 90cee0a5d5..0d59059290 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "test": "mocha --opts mocha.opts", "test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts", "quicktest": "mocha --opts mocha.opts", + "update-expected": "node -r sucrase/register ./test/js/update.ts && node -r sucrase/register ./test/parser/update.ts", "precoverage": "c8 mocha --opts mocha.coverage.opts", "coverage": "c8 report --reporter=text-lcov > coverage.lcov && c8 report --reporter=html", "codecov": "codecov", diff --git a/test/.eslintrc.json b/test/.eslintrc.json index d5ba8f9d9c..fc493fa889 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -1,5 +1,6 @@ { "rules": { - "no-console": "off" + "no-console": "off", + "@typescript-eslint/no-var-requires": "off" } } diff --git a/test/js/update.js b/test/js/update.js deleted file mode 100644 index fa9c834182..0000000000 --- a/test/js/update.js +++ /dev/null @@ -1,13 +0,0 @@ -// this file will replace all the expected.js files with their _actual -// equivalents. Only use it when you're sure that you haven't -// broken anything! -const fs = require("fs"); -const glob = require("tiny-glob/sync.js"); - -glob("samples/*/_actual.js", { cwd: __dirname }).forEach(file => { - const actual = fs.readFileSync(`${__dirname}/${file}`, "utf-8"); - fs.writeFileSync( - `${__dirname}/${file.replace("_actual.js", "expected.js")}`, - actual - ); -}); diff --git a/test/js/update.ts b/test/js/update.ts new file mode 100644 index 0000000000..d89226b8b2 --- /dev/null +++ b/test/js/update.ts @@ -0,0 +1,41 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { glob } from '../tiny-glob'; +// this file will replace all the expected.js files with their _actual +// equivalents. Only use it when you're sure that you haven't +// broken anything! + +const svelte = (function loadSvelte(test) { + process.env.TEST = test ? 'true' : ''; + const resolved = require.resolve('../../compiler.js'); + delete require.cache[resolved]; + return require(resolved); +})(false); + +function loadConfig(file) { + try { + const resolved = require.resolve(file); + delete require.cache[resolved]; + + const config = require(resolved); + return config.default || config; + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND') { + return {}; + } + + throw err; + } +} + +glob('samples/*/input.svelte', { cwd: __dirname }) + .forEach((file) => { + writeFileSync( + `${__dirname}/${file.replace('input.svelte', 'expected.js')}`, + svelte + .compile( + readFileSync(`${__dirname}/${file}`, 'utf-8').replace(/\s+$/, ''), + loadConfig(`${__dirname}/${file.replace('input.svelte', '_config.js')}`).options + ) + .js.code.replace(/generated by Svelte v\d+\.\d+\.\d+(-\w+\.\d+)?/, 'generated by Svelte vX.Y.Z') + ); + }); diff --git a/test/parser/update.js b/test/parser/update.js deleted file mode 100644 index 0edac895b5..0000000000 --- a/test/parser/update.js +++ /dev/null @@ -1,13 +0,0 @@ -// this file will replace all the output.json files with their _actual.json -// equivalents. Only use it when you're sure that you haven't -// broken anything! -const fs = require("fs"); -const glob = require("tiny-glob/sync.js"); - -glob("samples/*/_actual.json", { cwd: __dirname }).forEach(file => { - const actual = fs.readFileSync(`${__dirname}/${file}`, "utf-8"); - fs.writeFileSync( - `${__dirname}/${file.replace("_actual.json", "output.json")}`, - actual - ); -}); diff --git a/test/parser/update.ts b/test/parser/update.ts new file mode 100644 index 0000000000..5cc729725d --- /dev/null +++ b/test/parser/update.ts @@ -0,0 +1,32 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { glob } from '../tiny-glob'; + +// this file will replace all the expected.js files with their _actual +// equivalents. Only use it when you're sure that you haven't +// broken anything! +const svelte = (function loadSvelte(test) { + process.env.TEST = test ? 'true' : ''; + const resolved = require.resolve('../../compiler.js'); + delete require.cache[resolved]; + return require(resolved); +})(false); + +glob('samples/*/input.svelte', { cwd: __dirname }) + .forEach((file) => { + try { + writeFileSync( + `${__dirname}/${file.replace('input.svelte', 'output.json')}`, + JSON.stringify( + svelte.compile(readFileSync(`${__dirname}/${file}`, 'utf-8').replace(/\s+$/, ''), { generate: false }).ast, + null, + '\t' + ) + ); + } catch (e) { + if (e.name !== 'ParseError') throw e; + writeFileSync( + `${__dirname}/${file.replace('input.svelte', 'error.json')}`, + JSON.stringify({ ...e, message: e.message }, null, '\t') + ); + } + }); diff --git a/test/tiny-glob.ts b/test/tiny-glob.ts new file mode 100644 index 0000000000..8d2474bfb3 --- /dev/null +++ b/test/tiny-glob.ts @@ -0,0 +1,274 @@ +import { readdirSync, lstatSync, statSync } from 'fs'; +import { normalize, dirname, join, resolve, relative } from 'path'; + +// MIT +// tiny-glob, globrex and globalyzer by Terkel Gjervig + +const CHARS = { '{': '}', '(': ')', '[': ']' }; +const STRICT = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\)|(\\).|([@?!+*]\(.*\)))/; +const RELAXED = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; +const isWin = process.platform === 'win32'; +const SEP = isWin ? `\\\\+` : `\\/`; +const SEP_ESC = isWin ? `\\\\` : `/`; +const GLOBSTAR = `((?:[^/]*(?:/|$))*)`; +const WILDCARD = `([^/]*)`; +const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}]*(?:${SEP_ESC}|$))*)`; +const WILDCARD_SEGMENT = `([^${SEP_ESC}]*)`; +const isHidden = /(^|[\\/])\.[^\\/.]/g; +let CACHE = {}; +function isglob(str, { strict = true } = {}) { + if (str === '') return false; + let match; + const rgx = strict ? STRICT : RELAXED; + while ((match = rgx.exec(str))) { + if (match[2]) return true; + let idx = match.index + match[0].length; + const open = match[1]; + const close = open ? CHARS[open] : null; + let n; + if (open && close) if ((n = str.indexOf(close, idx)) !== -1) idx = n + 1; + str = str.slice(idx); + } + return false; +} +function parent(str, { strict = false } = {}) { + str = normalize(str).replace(/\/|\\/, '/'); + if (/[{[].*[/]*.*[}]]$/.test(str)) str += '/'; + str += 'a'; + do str = dirname(str); + while (isglob(str, { strict }) || /(^|[^\\])([{[]|\([^)]+$)/.test(str)); + return str.replace(/\\([*?|[\](){}])/g, '$1'); +} +function globalyzer(pattern, opts = {}) { + let base = parent(pattern, opts); + const isGlob = isglob(pattern, opts); + let glob; + if (base != '.') { + if ((glob = pattern.substr(base.length)).startsWith('/')) glob = glob.substr(1); + } else glob = pattern; + if (!isGlob) glob = (base = dirname(pattern)) !== '.' ? pattern.substr(base.length) : pattern; + if (glob.startsWith('./')) glob = glob.substr(2); + if (glob.startsWith('/')) glob = glob.substr(1); + return { base, glob, isGlob }; +} +function globrex(glob, { extended = false, globstar = false, strict = false, filepath = false, flags = '' } = {}) { + let regex = ''; + let segment = ''; + const path = { + regex: '', + segments: [], + globstar: undefined, + }; + let inGroup = false; + let inRange = false; + const ext = []; + function add(str, { split = false, last = false, only = '' } = {}) { + if (only !== 'path') regex += str; + if (filepath && only !== 'regex') { + path.regex += str === '\\/' ? SEP : str; + if (split) { + if (last) segment += str; + if (segment !== '') { + if (!flags.includes('g')) segment = `^${segment}$`; // change it 'includes' + path.segments.push(new RegExp(segment, flags)); + } + segment = ''; + } else { + segment += str; + } + } + } + const escaped = (condition, str = c) => add(condition ? str : `//${c}`); + let c; + let n; + for (let i = 0; i < glob.length; i++) { + c = glob[i]; + n = glob[i + 1]; + if (['\\', '$', '^', '.', '='].includes(c)) { + add(`\\${c}`); + continue; + } + switch (c) { + case '/': { + add(`\\${c}`, { split: true }); + if (n === '/' && !strict) regex += '?'; + break; + } + case '|': + case '(': { + escaped(ext.length); + break; + } + case ')': { + if (ext.length) { + add(c); + const type = ext.pop(); + if (type === '@') { + add('{1}'); + } else if (type === '!') { + add('([^/]*)'); + } else { + add(type); + } + } else add(`\\${c}`); + break; + } + case '+': { + if (n === '(' && extended) { + ext.push(c); + } else add(`\\${c}`); + break; + } + case '!': { + if (extended) { + if (inRange) { + add('^'); + break; + } else if (n === '(') { + ext.push(c); + i++; + } + } + escaped(extended && n === '(', '(?!'); + break; + } + case '?': { + if (extended && n === '(') { + ext.push(c); + } else { + escaped(extended, '.'); + } + break; + } + case '[': { + if (inRange && n === ':') { + i++; // skip [ + let value = ''; + while (glob[++i] !== ':') value += glob[i]; + if (value === 'alnum') add('(\\w|\\d)'); + else if (value === 'space') add('\\s'); + else if (value === 'digit') add('\\d'); + i++; // skip last ] + break; + } else if (extended) inRange = true; + escaped(extended); + break; + } + case ']': { + if (extended) inRange = false; + escaped(extended); + break; + } + case '{': { + if (extended) inGroup = true; + escaped(extended, '('); + break; + } + case '}': { + if (extended) inGroup = false; + escaped(extended, ')'); + break; + } + case ',': { + escaped(inGroup, '|'); + break; + } + case '*': { + if (n === '(' && extended) { + ext.push(c); + break; + } + const prevChar = glob[i - 1]; + let starCount = 1; + while (glob[i + 1] === '*') { + starCount++; + i++; + } + const nextChar = glob[i + 1]; + if (!globstar) add('.*'); + else { + const isGlobstar = + starCount > 1 && (prevChar === '/' || prevChar === void 0) && (nextChar === '/' || nextChar === void 0); + if (isGlobstar) { + add(GLOBSTAR, { only: 'regex' }); + add(GLOBSTAR_SEGMENT, { only: 'path', last: true, split: true }); + i++; + } else { + add(WILDCARD, { only: 'regex' }); + add(WILDCARD_SEGMENT, { only: 'path' }); + } + } + break; + } + case '@': { + if (extended && n === '(') ext.push(c); + else add(c); + break; + } + default: + add(c); + } + } + const g = flags.includes('g'); + return { + regex: new RegExp(g ? regex : `^${regex}$`, flags), + path: filepath + ? { + segments: [...path.segments, new RegExp(g ? segment : `^${segment}$`, flags)], + regex: new RegExp(g ? path.regex : `^${path.regex}$`, flags), + globstar: new RegExp(!g ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, flags), + } + : undefined, + }; +} +function walk(output, prefix, lexer, filesOnly, dot, cwd, dirname = '', level = 0) { + const rgx = lexer.segments[level]; + const dir = join(cwd, prefix, dirname); + const files = readdirSync(dir); + let i = 0; + let file; + const len = files.length; + + let fullpath; + let relpath; + let stats; + let isMatch; + for (; i < len; i++) { + fullpath = join(dir, (file = files[i])); + relpath = dirname ? join(dirname, file) : file; + if (!dot && isHidden.test(relpath)) continue; + isMatch = lexer.regex.test(relpath); + if ((stats = CACHE[relpath]) === void 0) CACHE[relpath] = stats = lstatSync(fullpath); + if (!stats.isDirectory()) { + isMatch && output.push(relative(cwd, fullpath)); + continue; + } + if (rgx && !rgx.test(file)) continue; + if (!filesOnly && isMatch) output.push(join(prefix, relpath)); + walk(output, prefix, lexer, filesOnly, dot, cwd, relpath, rgx && rgx.toString() !== lexer.globstar && ++level); + } +} +export function glob(str: string, { cwd = '.', absolute = false, filesOnly = false, dot = false, flush = false }) { + if (!str) return []; + const glob = globalyzer(str); + if (!glob.isGlob) { + try { + const resolved = resolve(cwd, str); + const dirent = statSync(resolved); + if (filesOnly && !dirent.isFile()) return []; + + return absolute ? [resolved] : [str]; + } catch (err) { + if (err.code != 'ENOENT') throw err; + + return []; + } + } + if (flush) CACHE = {}; + const matches = []; + const { path } = globrex(glob.glob, { filepath: true, globstar: true, extended: true }); + //@ts-ignore + path.globstar = path.globstar.toString(); + walk(matches, glob.base, path, filesOnly, dot, cwd, '.', 0); + return absolute ? matches.map((x) => resolve(cwd, x)) : matches; +}