From 507233d36db26b4a9d5544d80d7469411ef4f037 Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 27 May 2020 15:39:49 +0200 Subject: [PATCH 1/4] init --- package.json | 2 +- test/js/update.js | 13 -- test/js/update.ts | 39 ++++++ test/parser/update.js | 13 -- test/parser/update.ts | 31 +++++ test/tiny-glob.ts | 274 ++++++++++++++++++++++++++++++++++++++++ test/validator/index.ts | 104 +++++++++++++++ 7 files changed, 449 insertions(+), 27 deletions(-) delete mode 100644 test/js/update.js create mode 100644 test/js/update.ts delete mode 100644 test/parser/update.js create mode 100644 test/parser/update.ts create mode 100644 test/tiny-glob.ts create mode 100644 test/validator/index.ts diff --git a/package.json b/package.json index 90cee0a5d5..bfbd207e99 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", @@ -88,7 +89,6 @@ "rollup": "^1.27.14", "source-map": "^0.7.3", "source-map-support": "^0.5.13", - "tiny-glob": "^0.2.6", "tslib": "^1.10.0", "typescript": "^3.5.3" }, 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..03cb252921 --- /dev/null +++ b/test/js/update.ts @@ -0,0 +1,39 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; +// 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; + } +} +require(resolve(__dirname, '../tiny-glob.ts')) + .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..c08e244f0c --- /dev/null +++ b/test/parser/update.ts @@ -0,0 +1,31 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; +// 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); +require(resolve(__dirname, '../tiny-glob.ts')) + .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; +} diff --git a/test/validator/index.ts b/test/validator/index.ts new file mode 100644 index 0000000000..c4bf0f2cf6 --- /dev/null +++ b/test/validator/index.ts @@ -0,0 +1,104 @@ +import * as fs from 'fs'; +import { assert } from '../test'; +import { svelte, loadConfig, tryToLoadJson } from '../helpers'; + +describe("validate", () => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { + if (dir[0] === ".") return; + + // add .solo to a sample directory name to only run that test + const solo = /\.solo/.test(dir); + const skip = /\.skip/.test(dir); + + if (solo && process.env.CI) { + throw new Error("Forgot to remove `solo: true` from test"); + } + + (solo ? it.only : skip ? it.skip : it)(dir, () => { + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + + const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); + const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || []; + const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`); + const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`); + + let error; + + try { + const { warnings } = svelte.compile(input, { + dev: config.dev, + legacy: config.legacy, + generate: false, + customElement: config.customElement, + ...options, + }); + + assert.deepEqual(warnings.map(w => ({ + code: w.code, + message: w.message, + pos: w.pos, + start: w.start, + end: w.end + })), expected_warnings); + } catch (e) { + error = e; + } + + const expected = expected_errors && expected_errors[0]; + + if (error || expected) { + if (error && !expected) { + throw error; + } + + if (expected && !error) { + throw new Error(`Expected an error: ${expected.message}`); + } + + try { + assert.equal(error.code, expected.code); + assert.equal(error.message, expected.message); + assert.deepEqual(error.start, expected.start); + assert.deepEqual(error.end, expected.end); + assert.equal(error.pos, expected.pos); + } catch (e) { + console.error(error); // eslint-disable-line no-console + throw e; + } + } + }); + }); + + it("errors if options.name is illegal", () => { + assert.throws(() => { + svelte.compile("
", { + name: "not.valid", + generate: false + }); + }, /options\.name must be a valid identifier/); + }); + + it("warns if options.name is not capitalised", () => { + const { warnings } = svelte.compile("
", { + name: "lowercase", + generate: false + }); + + assert.deepEqual(warnings.map(w => ({ + code: w.code, + message: w.message + })), [{ + code: `options-lowercase-name`, + message: "options.name should be capitalised" + }]); + }); + + it("does not warn if options.name begins with non-alphabetic character", () => { + const { warnings } = svelte.compile("
", { + name: "_", + generate: false + }); + + assert.deepEqual(warnings, []); + }); +}); \ No newline at end of file From 76ab665354dfb8357202ca16740e7d002b86c647 Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 27 May 2020 15:40:44 +0200 Subject: [PATCH 2/4] remove file --- test/validator/index.ts | 104 ---------------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 test/validator/index.ts diff --git a/test/validator/index.ts b/test/validator/index.ts deleted file mode 100644 index c4bf0f2cf6..0000000000 --- a/test/validator/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import * as fs from 'fs'; -import { assert } from '../test'; -import { svelte, loadConfig, tryToLoadJson } from '../helpers'; - -describe("validate", () => { - fs.readdirSync(`${__dirname}/samples`).forEach(dir => { - if (dir[0] === ".") return; - - // add .solo to a sample directory name to only run that test - const solo = /\.solo/.test(dir); - const skip = /\.skip/.test(dir); - - if (solo && process.env.CI) { - throw new Error("Forgot to remove `solo: true` from test"); - } - - (solo ? it.only : skip ? it.skip : it)(dir, () => { - const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); - - const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); - const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || []; - const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`); - const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`); - - let error; - - try { - const { warnings } = svelte.compile(input, { - dev: config.dev, - legacy: config.legacy, - generate: false, - customElement: config.customElement, - ...options, - }); - - assert.deepEqual(warnings.map(w => ({ - code: w.code, - message: w.message, - pos: w.pos, - start: w.start, - end: w.end - })), expected_warnings); - } catch (e) { - error = e; - } - - const expected = expected_errors && expected_errors[0]; - - if (error || expected) { - if (error && !expected) { - throw error; - } - - if (expected && !error) { - throw new Error(`Expected an error: ${expected.message}`); - } - - try { - assert.equal(error.code, expected.code); - assert.equal(error.message, expected.message); - assert.deepEqual(error.start, expected.start); - assert.deepEqual(error.end, expected.end); - assert.equal(error.pos, expected.pos); - } catch (e) { - console.error(error); // eslint-disable-line no-console - throw e; - } - } - }); - }); - - it("errors if options.name is illegal", () => { - assert.throws(() => { - svelte.compile("
", { - name: "not.valid", - generate: false - }); - }, /options\.name must be a valid identifier/); - }); - - it("warns if options.name is not capitalised", () => { - const { warnings } = svelte.compile("
", { - name: "lowercase", - generate: false - }); - - assert.deepEqual(warnings.map(w => ({ - code: w.code, - message: w.message - })), [{ - code: `options-lowercase-name`, - message: "options.name should be capitalised" - }]); - }); - - it("does not warn if options.name begins with non-alphabetic character", () => { - const { warnings } = svelte.compile("
", { - name: "_", - generate: false - }); - - assert.deepEqual(warnings, []); - }); -}); \ No newline at end of file From 00bb5d103a5a268f79dcb95213a7d176747ad21e Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 27 May 2020 15:44:01 +0200 Subject: [PATCH 3/4] restore tiny-glob --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index bfbd207e99..0d59059290 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "rollup": "^1.27.14", "source-map": "^0.7.3", "source-map-support": "^0.5.13", + "tiny-glob": "^0.2.6", "tslib": "^1.10.0", "typescript": "^3.5.3" }, From d7708c5c92cf6efddb0f25e833cba2648113aa6e Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 27 May 2020 16:01:39 +0200 Subject: [PATCH 4/4] lint --- test/.eslintrc.json | 3 ++- test/js/update.ts | 8 +++++--- test/parser/update.ts | 7 ++++--- 3 files changed, 11 insertions(+), 7 deletions(-) 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.ts b/test/js/update.ts index 03cb252921..d89226b8b2 100644 --- a/test/js/update.ts +++ b/test/js/update.ts @@ -1,14 +1,16 @@ import { readFileSync, writeFileSync } from 'fs'; -import { resolve } from 'path'; +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); @@ -24,8 +26,8 @@ function loadConfig(file) { throw err; } } -require(resolve(__dirname, '../tiny-glob.ts')) - .glob('samples/*/input.svelte', { cwd: __dirname }) + +glob('samples/*/input.svelte', { cwd: __dirname }) .forEach((file) => { writeFileSync( `${__dirname}/${file.replace('input.svelte', 'expected.js')}`, diff --git a/test/parser/update.ts b/test/parser/update.ts index c08e244f0c..5cc729725d 100644 --- a/test/parser/update.ts +++ b/test/parser/update.ts @@ -1,5 +1,6 @@ import { readFileSync, writeFileSync } from 'fs'; -import { resolve } from 'path'; +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! @@ -9,8 +10,8 @@ const svelte = (function loadSvelte(test) { delete require.cache[resolved]; return require(resolved); })(false); -require(resolve(__dirname, '../tiny-glob.ts')) - .glob('samples/*/input.svelte', { cwd: __dirname }) + +glob('samples/*/input.svelte', { cwd: __dirname }) .forEach((file) => { try { writeFileSync(