more tidying up

pull/2252/head
Richard Harris 7 years ago
parent ba33f278be
commit a236794763

@ -2,9 +2,8 @@ import MagicString, { Bundle } from 'magic-string';
import { walk, childKeys } from 'estree-walker'; import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character'; import { getLocator } from 'locate-character';
import Stats from '../Stats'; import Stats from '../Stats';
import { reserved } from '../utils/names'; import { globals, reserved } from '../utils/names';
import { namespaces, validNamespaces } from '../utils/namespaces'; import { namespaces, validNamespaces } from '../utils/namespaces';
import { removeNode } from '../utils/removeNode';
import wrapModule from './wrapModule'; import wrapModule from './wrapModule';
import { create_scopes, extract_names, Scope } from './utils/scope'; import { create_scopes, extract_names, Scope } from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
@ -13,14 +12,13 @@ import Fragment from './nodes/Fragment';
import internal_exports from './internal-exports'; import internal_exports from './internal-exports';
import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces'; import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame'; import get_code_frame from '../utils/get_code_frame';
import flatten_reference from './utils/flatten_reference'; import flatten_reference from './utils/flatten_reference';
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import TemplateScope from './nodes/shared/TemplateScope'; import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import { remove_indentation, add_indentation } from '../utils/indentation'; import { remove_indentation, add_indentation } from '../utils/indentation';
import getObject from '../utils/getObject'; import get_object from './utils/get_object';
import globalWhitelist from '../utils/globalWhitelist';
type ComponentOptions = { type ComponentOptions = {
namespace?: string; namespace?: string;
@ -36,6 +34,34 @@ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else'];
childKeys.Attribute = ['value']; childKeys.Attribute = ['value'];
childKeys.ExportNamedDeclaration = ['declaration', 'specifiers']; childKeys.ExportNamedDeclaration = ['declaration', 'specifiers'];
function remove_node(code: MagicString, start: number, end: number, body: Node, node: Node) {
const i = body.indexOf(node);
if (i === -1) throw new Error('node not in list');
let a;
let b;
if (body.length === 1) {
// remove everything, leave {}
a = start;
b = end;
} else if (i === 0) {
// remove everything before second node, including comments
a = start;
while (/\s/.test(code.original[a])) a += 1;
b = body[i].end;
while (/[\s,]/.test(code.original[b])) b += 1;
} else {
// remove the end of the previous node to the end of this one
a = body[i - 1].end;
b = node.end;
}
code.remove(a, b);
return;
}
export default class Component { export default class Component {
stats: Stats; stats: Stats;
warnings: Warning[]; warnings: Warning[];
@ -392,7 +418,7 @@ export default class Component {
const start = this.locator(pos.start); const start = this.locator(pos.start);
const end = this.locator(pos.end); const end = this.locator(pos.end);
const frame = getCodeFrame(this.source, start.line - 1, start.column); const frame = get_code_frame(this.source, start.line - 1, start.column);
this.warnings.push({ this.warnings.push({
code: warning.code, code: warning.code,
@ -412,7 +438,7 @@ export default class Component {
content.body.forEach(node => { content.body.forEach(node => {
if (node.type === 'ImportDeclaration') { if (node.type === 'ImportDeclaration') {
// imports need to be hoisted out of the IIFE // imports need to be hoisted out of the IIFE
removeNode(code, content.start, content.end, content.body, node); remove_node(code, content.start, content.end, content.body, node);
this.imports.push(node); this.imports.push(node);
} }
}); });
@ -453,7 +479,7 @@ export default class Component {
code.remove(node.start, node.declaration.start); code.remove(node.start, node.declaration.start);
} else { } else {
removeNode(code, content.start, content.end, content.body, node); remove_node(code, content.start, content.end, content.body, node);
node.specifiers.forEach(specifier => { node.specifiers.forEach(specifier => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
@ -662,10 +688,10 @@ export default class Component {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = deep names = deep
? [getObject(node.left).name] ? [get_object(node.left).name]
: extract_names(node.left); : extract_names(node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
names = [getObject(node.argument).name]; names = [get_object(node.argument).name];
} }
if (names) { if (names) {
@ -699,7 +725,7 @@ export default class Component {
} }
if (is_reference(node, parent)) { if (is_reference(node, parent)) {
const object = getObject(node); const object = get_object(node);
const { name } = object; const { name } = object;
if (name[0] === '$' && !scope.has(name)) { if (name[0] === '$' && !scope.has(name)) {
@ -1028,14 +1054,14 @@ export default class Component {
} }
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const identifier = getObject(node.left) const identifier = get_object(node.left)
assignee_nodes.add(identifier); assignee_nodes.add(identifier);
assignees.add(identifier.name); assignees.add(identifier.name);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = getObject(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
} else if (is_reference(node, parent)) { } else if (is_reference(node, parent)) {
const identifier = getObject(node); const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) { if (!assignee_nodes.has(identifier)) {
const { name } = identifier; const { name } = identifier;
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
@ -1149,7 +1175,7 @@ export default class Component {
if (this.var_lookup.has(name)) return; if (this.var_lookup.has(name)) return;
if (template_scope && template_scope.names.has(name)) return; if (template_scope && template_scope.names.has(name)) return;
if (globalWhitelist.has(name)) return; if (globals.has(name)) return;
let message = `'${name}' is not defined`; let message = `'${name}' is not defined`;
if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`; if (!this.ast.instance) message += `. Consider adding a <script> block with 'export let ${name}' to declare a prop`;

@ -1,13 +1,24 @@
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import Selector from './Selector'; import Selector from './Selector';
import hash from '../../utils/hash';
import removeCSSPrefix from '../../utils/removeCSSPrefix';
import Element from '../nodes/Element'; import Element from '../nodes/Element';
import { Node, Ast } from '../../interfaces'; import { Node, Ast } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes' function remove_css_prefox(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}
const isKeyframesNode = (node: Node) => remove_css_prefox(node.name) === 'keyframes';
// https://github.com/darkskyapp/string-hash/blob/master/index.js
function hash(str: string): string {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36);
}
class Rule { class Rule {
selectors: Selector[]; selectors: Selector[];
@ -97,7 +108,7 @@ class Declaration {
} }
transform(code: MagicString, keyframes: Map<string, string>) { transform(code: MagicString, keyframes: Map<string, string>) {
const property = this.node.property && removeCSSPrefix(this.node.property.toLowerCase()); const property = this.node.property && remove_css_prefox(this.node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') { if (property === 'animation' || property === 'animation-name') {
this.node.value.children.forEach((block: Node) => { this.node.value.children.forEach((block: Node) => {
if (block.type === 'Identifier') { if (block.type === 'Identifier') {

@ -1,5 +1,5 @@
import Node from './shared/Node'; import Node from './shared/Node';
import getObject from '../../utils/getObject'; import get_object from '../utils/get_object';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
@ -27,7 +27,7 @@ export default class Binding extends Node {
let obj; let obj;
let prop; let prop;
const { name } = getObject(this.expression.node); const { name } = get_object(this.expression.node);
this.isContextual = scope.names.has(name); this.isContextual = scope.names.has(name);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref

@ -1,4 +1,4 @@
import isVoidElementName from '../../utils/isVoidElementName'; import { is_void } from '../../utils/names';
import Node from './shared/Node'; import Node from './shared/Node';
import Attribute from './Attribute'; import Attribute from './Attribute';
import Binding from './Binding'; import Binding from './Binding';
@ -557,7 +557,7 @@ export default class Element extends Node {
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on SVG elements` message: `'${binding.name}' is not a valid binding on SVG elements`
}); });
} else if (isVoidElementName(this.name)) { } else if (is_void(this.name)) {
component.error(binding, { component.error(binding, {
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead` message: `'${binding.name}' is not a valid binding on void elements like <${this.name}>. Use a wrapper element instead`

@ -4,12 +4,12 @@ import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference'; import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope'; import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import globalWhitelist from '../../../utils/globalWhitelist'; import { globals } from '../../../utils/names';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import Wrapper from '../../render-dom/wrappers/shared/Wrapper'; import Wrapper from '../../render-dom/wrappers/shared/Wrapper';
import sanitize from '../../../utils/sanitize'; import { sanitize } from '../../../utils/names';
import TemplateScope from './TemplateScope'; import TemplateScope from './TemplateScope';
import getObject from '../../../utils/getObject'; import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match'; import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block'; import Block from '../../render-dom/Block';
@ -123,7 +123,7 @@ export default class Expression {
if (scope.has(name)) return; if (scope.has(name)) return;
if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return; if (globals.has(name) && !component.var_lookup.has(name)) return;
if (name[0] === '$' && template_scope.names.has(name.slice(1))) { if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, { component.error(node, {
@ -164,10 +164,10 @@ export default class Expression {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = deep names = deep
? [getObject(node.left).name] ? [get_object(node.left).name]
: extract_names(node.left); : extract_names(node.left);
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument); const { name } = get_object(node.argument);
names = [name]; names = [name];
} }
} }
@ -257,7 +257,7 @@ export default class Expression {
const { name, nodes } = flatten_reference(node); const { name, nodes } = flatten_reference(node);
if (scope.has(name)) return; if (scope.has(name)) return;
if (globalWhitelist.has(name) && !component.var_lookup.has(name)) return; if (globals.has(name) && !component.var_lookup.has(name)) return;
if (function_expression) { if (function_expression) {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
@ -289,7 +289,7 @@ export default class Expression {
if (function_expression) { if (function_expression) {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const names = node.left.type === 'MemberExpression' const names = node.left.type === 'MemberExpression'
? [getObject(node.left).name] ? [get_object(node.left).name]
: extract_names(node.left); : extract_names(node.left);
if (node.operator === '=' && nodes_match(node.left, node.right)) { if (node.operator === '=' && nodes_match(node.left, node.right)) {
@ -311,7 +311,7 @@ export default class Expression {
}); });
} }
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument); const { name } = get_object(node.argument);
if (scope.declarations.has(name)) return; if (scope.declarations.has(name)) return;

@ -7,10 +7,10 @@ import { CompileOptions } from '../../interfaces';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import { stringify_props } from '../utils/stringify_props'; import { stringify_props } from '../utils/stringify_props';
import add_to_set from '../utils/add_to_set'; import add_to_set from '../utils/add_to_set';
import getObject from '../../utils/getObject'; import get_object from '../utils/get_object';
import { extract_names } from '../utils/scope'; import { extract_names } from '../utils/scope';
import { nodes_match } from '../../utils/nodes_match'; import { nodes_match } from '../../utils/nodes_match';
import sanitize from '../../utils/sanitize'; import { sanitize } from '../../utils/names';
export default function dom( export default function dom(
component: Component, component: Component,
@ -168,7 +168,7 @@ export default function dom(
let names = []; let names = [];
if (node.left.type === 'MemberExpression') { if (node.left.type === 'MemberExpression') {
const left_object_name = getObject(node.left).name; const left_object_name = get_object(node.left).name;
left_object_name && (names = [left_object_name]); left_object_name && (names = [left_object_name]);
} else { } else {
names = extract_names(node.left); names = extract_names(node.left);
@ -197,7 +197,7 @@ export default function dom(
} }
else if (node.type === 'UpdateExpression') { else if (node.type === 'UpdateExpression') {
const { name } = getObject(node.argument); const { name } = get_object(node.argument);
if (scope.find_owner(name) !== component.instance_scope) return; if (scope.find_owner(name) !== component.instance_scope) return;

@ -1,13 +1,13 @@
import Binding from '../../../nodes/Binding'; import Binding from '../../../nodes/Binding';
import ElementWrapper from '.'; import ElementWrapper from '.';
import { dimensions } from '../../../../utils/patterns'; import { dimensions } from '../../../../utils/patterns';
import getObject from '../../../../utils/getObject'; import get_object from '../../../utils/get_object';
import Block from '../../Block'; import Block from '../../Block';
import Node from '../../../nodes/shared/Node'; import Node from '../../../nodes/shared/Node';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import { get_tail } from '../../../../utils/get_tail_snippet';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import { Node as INode } from '../../../../interfaces';
// TODO this should live in a specific binding // TODO this should live in a specific binding
const readOnlyMediaAttributes = new Set([ const readOnlyMediaAttributes = new Set([
@ -17,6 +17,12 @@ const readOnlyMediaAttributes = new Set([
'played' 'played'
]); ]);
function get_tail(node: INode) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
return { start: node.end, end };
}
export default class BindingWrapper { export default class BindingWrapper {
node: Binding; node: Binding;
parent: ElementWrapper; parent: ElementWrapper;
@ -52,13 +58,13 @@ export default class BindingWrapper {
if (node.isContextual) { if (node.isContextual) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
const { name } = getObject(this.node.expression.node); const { name } = get_object(this.node.expression.node);
const eachBlock = this.parent.node.scope.getOwner(name); const eachBlock = this.parent.node.scope.getOwner(name);
(eachBlock as EachBlock).has_binding = true; (eachBlock as EachBlock).has_binding = true;
} }
this.object = getObject(this.node.expression.node).name; this.object = get_object(this.node.expression.node).name;
// TODO unfortunate code is necessary because we need to use `ctx` // TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script> // inside the fragment, but not inside the <script>

@ -3,8 +3,7 @@ import Element from '../../../nodes/Element';
import Wrapper from '../shared/Wrapper'; import Wrapper from '../shared/Wrapper';
import Block from '../../Block'; import Block from '../../Block';
import Node from '../../../nodes/shared/Node'; import Node from '../../../nodes/shared/Node';
import { quote_prop_if_necessary, quote_name_if_necessary } from '../../../../utils/names'; import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names';
import isVoidElementName from '../../../../utils/isVoidElementName';
import FragmentWrapper from '../Fragment'; import FragmentWrapper from '../Fragment';
import { stringify, escapeHTML, escape } from '../../../../utils/stringify'; import { stringify, escapeHTML, escape } from '../../../../utils/stringify';
import TextWrapper from '../Text'; import TextWrapper from '../Text';
@ -20,7 +19,6 @@ import add_to_set from '../../../utils/add_to_set';
import addEventHandlers from '../shared/addEventHandlers'; import addEventHandlers from '../shared/addEventHandlers';
import addActions from '../shared/addActions'; import addActions from '../shared/addActions';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import sanitize from '../../../../utils/sanitize';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
const events = [ const events = [
@ -339,7 +337,7 @@ export default class ElementWrapper extends Wrapper {
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}` open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`
}); });
if (isVoidElementName(wrapper.node.name)) return open + '>'; if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${wrapper.fragment.nodes.map(toHTML).join('')}</${wrapper.node.name}>`; return `${open}>${wrapper.fragment.nodes.map(toHTML).join('')}</${wrapper.node.name}>`;
} }

@ -3,15 +3,14 @@ import Renderer from '../../Renderer';
import Block from '../../Block'; import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent'; import InlineComponent from '../../../nodes/InlineComponent';
import FragmentWrapper from '../Fragment'; import FragmentWrapper from '../Fragment';
import { quote_name_if_necessary, quote_prop_if_necessary } from '../../../../utils/names'; import { quote_name_if_necessary, quote_prop_if_necessary, sanitize } from '../../../../utils/names';
import { stringify_props } from '../../../utils/stringify_props'; import { stringify_props } from '../../../utils/stringify_props';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import deindent from '../../../utils/deindent'; import deindent from '../../../utils/deindent';
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import getObject from '../../../../utils/getObject'; import get_object from '../../../utils/get_object';
import flatten_reference from '../../../utils/flatten_reference'; import flatten_reference from '../../../utils/flatten_reference';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import sanitize from '../../../../utils/sanitize';
import { get_context_merger } from '../shared/get_context_merger'; import { get_context_merger } from '../shared/get_context_merger';
import EachBlock from '../../../nodes/EachBlock'; import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope'; import TemplateScope from '../../../nodes/shared/TemplateScope';
@ -46,7 +45,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (binding.isContextual) { if (binding.isContextual) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
const { name } = getObject(binding.expression.node); const { name } = get_object(binding.expression.node);
const eachBlock = this.node.scope.getOwner(name); const eachBlock = this.node.scope.getOwner(name);
(eachBlock as EachBlock).has_binding = true; (eachBlock as EachBlock).has_binding = true;

@ -4,9 +4,9 @@ import Block from '../Block';
import Slot from '../../nodes/Slot'; import Slot from '../../nodes/Slot';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import sanitize from '../../../utils/sanitize'; import { sanitize } from '../../../utils/names';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props'; import { stringify_props } from '../../utils/stringify_props';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';

@ -1,5 +1,4 @@
import { quote_prop_if_necessary, quote_name_if_necessary } from '../../../utils/names'; import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../../../utils/names';
import isVoidElementName from '../../../utils/isVoidElementName';
import Attribute from '../../nodes/Attribute'; import Attribute from '../../nodes/Attribute';
import Node from '../../nodes/shared/Node'; import Node from '../../nodes/shared/Node';
import { snip } from '../../../utils/snip'; import { snip } from '../../../utils/snip';
@ -149,7 +148,7 @@ export default function(node, renderer, options) {
renderer.render(node.children, options); renderer.render(node.children, options);
} }
if (!isVoidElementName(node.name)) { if (!is_void(node.name)) {
renderer.append(`</${node.name}>`); renderer.append(`</${node.name}>`);
} }
} }

@ -1,5 +1,5 @@
import { quote_prop_if_necessary } from '../../../utils/names'; import { quote_prop_if_necessary } from '../../../utils/names';
import get_slot_data from '../../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
export default function(node, renderer, options) { export default function(node, renderer, options) {
const name = node.attributes.find(attribute => attribute.name === 'name'); const name = node.attributes.find(attribute => attribute.name === 'name');

@ -1,6 +1,6 @@
import { Node } from '../interfaces'; import { Node } from '../../interfaces';
export default function getObject(node: Node) { export default function get_object(node: Node) {
while (node.type === 'ParenthesizedExpression') node = node.expression; while (node.type === 'ParenthesizedExpression') node = node.expression;
while (node.type === 'MemberExpression') node = node.object; while (node.type === 'MemberExpression') node = node.object;
return node; return node;

@ -1,5 +1,5 @@
import { snip } from './snip'; import { snip } from '../../utils/snip';
import { stringify_attribute } from './stringify_attribute'; import { stringify_attribute } from '../../utils/stringify_attribute';
export default function get_slot_data(attributes, is_ssr: boolean) { export default function get_slot_data(attributes, is_ssr: boolean) {
return attributes return attributes

@ -2,7 +2,7 @@ import readExpression from '../read/expression';
import readScript from '../read/script'; import readScript from '../read/script';
import readStyle from '../read/style'; import readStyle from '../read/style';
import { decodeCharacterReferences } from '../utils/html'; import { decodeCharacterReferences } from '../utils/html';
import isVoidElementName from '../../utils/isVoidElementName'; import { is_void } from '../../utils/names';
import { Parser } from '../index'; import { Parser } from '../index';
import { Node } from '../../interfaces'; import { Node } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
@ -146,7 +146,7 @@ export default function tag(parser: Parser) {
parser.allowWhitespace(); parser.allowWhitespace();
if (isClosingTag) { if (isClosingTag) {
if (isVoidElementName(name)) { if (is_void(name)) {
parser.error({ parser.error({
code: `invalid-void-content`, code: `invalid-void-content`,
message: `<${name}> is a void element and cannot have children, or a closing tag` message: `<${name}> is a void element and cannot have children, or a closing tag`
@ -244,7 +244,7 @@ export default function tag(parser: Parser) {
parser.current().children.push(element); parser.current().children.push(element);
const selfClosing = parser.eat('/') || isVoidElementName(name); const selfClosing = parser.eat('/') || is_void(name);
parser.eat('>', true); parser.eat('>', true);

@ -1,5 +1,4 @@
import { SourceMap } from 'magic-string'; import { SourceMap } from 'magic-string';
import replaceAsync from '../utils/replaceAsync';
export interface PreprocessorGroup { export interface PreprocessorGroup {
markup?: (options: { markup?: (options: {
@ -37,6 +36,39 @@ function parseAttributes(str: string) {
return attrs; return attrs;
} }
interface Replacement {
offset: number;
length: number;
replacement: string;
}
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<string>) {
const replacements: Promise<Replacement>[] = [];
str.replace(re, (...args) => {
replacements.push(
func(...args).then(
res =>
<Replacement>({
offset: args[args.length - 2],
length: args[0].length,
replacement: res,
})
)
);
return '';
});
let out = '';
let lastEnd = 0;
for (const { offset, length, replacement } of await Promise.all(
replacements
)) {
out += str.slice(lastEnd, offset) + replacement;
lastEnd = offset + length;
}
out += str.slice(lastEnd);
return out;
}
export default async function preprocess( export default async function preprocess(
source: string, source: string,
preprocessor: PreprocessorGroup | PreprocessorGroup[], preprocessor: PreprocessorGroup | PreprocessorGroup[],
@ -61,7 +93,7 @@ export default async function preprocess(
} }
for (const fn of script) { for (const fn of script) {
source = await replaceAsync( source = await replace_async(
source, source,
/<script([^]*?)>([^]*?)<\/script>/gi, /<script([^]*?)>([^]*?)<\/script>/gi,
async (match, attributes, content) => { async (match, attributes, content) => {
@ -77,7 +109,7 @@ export default async function preprocess(
} }
for (const fn of style) { for (const fn of style) {
source = await replaceAsync( source = await replace_async(
source, source,
/<style([^]*?)>([^]*?)<\/style>/gi, /<style([^]*?)>([^]*?)<\/style>/gi,
async (match, attributes, content) => { async (match, attributes, content) => {

@ -1,5 +1,5 @@
import { locate } from 'locate-character'; import { locate } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame'; import get_code_frame from '../utils/get_code_frame';
class CompileError extends Error { class CompileError extends Error {
code: string; code: string;
@ -34,7 +34,7 @@ export default function error(message: string, props: {
error.pos = props.start; error.pos = props.start;
error.filename = props.filename; error.filename = props.filename;
error.frame = getCodeFrame(props.source, start.line - 1, start.column); error.frame = get_code_frame(props.source, start.line - 1, start.column);
throw error; throw error;
} }

@ -53,30 +53,28 @@ function levenshtein(str1: string, str2: string) {
return current.pop(); return current.pop();
} }
const _nonWordRe = /[^\w, ]+/; const non_word_regex = /[^\w, ]+/;
function _iterateGrams(value: string, gramSize: number) { function iterate_grams(value: string, gram_size = 2) {
gramSize = gramSize || 2; const simplified = '-' + value.toLowerCase().replace(non_word_regex, '') + '-';
const simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-'; const len_diff = gram_size - simplified.length;
const lenDiff = gramSize - simplified.length;
const results = []; const results = [];
if (lenDiff > 0) { if (len_diff > 0) {
for (let i = 0; i < lenDiff; ++i) { for (let i = 0; i < len_diff; ++i) {
value += '-'; value += '-';
} }
} }
for (let i = 0; i < simplified.length - gramSize + 1; ++i) { for (let i = 0; i < simplified.length - gram_size + 1; ++i) {
results.push(simplified.slice(i, i + gramSize)); results.push(simplified.slice(i, i + gram_size));
} }
return results; return results;
} }
function _gramCounter(value: string, gramSize: number) { function gram_counter(value: string, gram_size = 2) {
// return an object where key=gram, value=number of occurrences // return an object where key=gram, value=number of occurrences
gramSize = gramSize || 2;
const result = {}; const result = {};
const grams = _iterateGrams(value, gramSize); const grams = iterate_grams(value, gram_size);
let i = 0; let i = 0;
for (i; i < grams.length; ++i) { for (i; i < grams.length; ++i) {
@ -89,25 +87,21 @@ function _gramCounter(value: string, gramSize: number) {
return result; return result;
} }
function sortDescending(a, b) { function sort_descending(a, b) {
return b[0] - a[0]; return b[0] - a[0];
} }
class FuzzySet { class FuzzySet {
exactSet: object; exact_set = {};
matchDict: object; match_dict = {};
items: object; items = {};
constructor(arr: string[]) { constructor(arr: string[]) {
// define all the object functions and attributes
this.exactSet = {};
this.matchDict = {};
this.items = {};
// initialization // initialization
for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) { for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) {
this.items[i] = []; this.items[i] = [];
} }
// add all the items to the set // add all the items to the set
for (let i = 0; i < arr.length; ++i) { for (let i = 0; i < arr.length; ++i) {
this.add(arr[i]); this.add(arr[i]);
@ -115,8 +109,8 @@ class FuzzySet {
} }
add(value: string) { add(value: string) {
const normalizedValue = value.toLowerCase(); const normalized_value = value.toLowerCase();
if (normalizedValue in this.exactSet) { if (normalized_value in this.exact_set) {
return false; return false;
} }
@ -126,35 +120,35 @@ class FuzzySet {
} }
} }
_add(value: string, gramSize: number) { _add(value: string, gram_size: number) {
const normalizedValue = value.toLowerCase(); const normalized_value = value.toLowerCase();
const items = this.items[gramSize] || []; const items = this.items[gram_size] || [];
const index = items.length; const index = items.length;
items.push(0); items.push(0);
const gramCounts = _gramCounter(normalizedValue, gramSize); const gram_counts = gram_counter(normalized_value, gram_size);
let sumOfSquareGramCounts = 0; let sum_of_square_gram_counts = 0;
let gram; let gram;
let gramCount; let gram_count;
for (gram in gramCounts) { for (gram in gram_counts) {
gramCount = gramCounts[gram]; gram_count = gram_counts[gram];
sumOfSquareGramCounts += Math.pow(gramCount, 2); sum_of_square_gram_counts += Math.pow(gram_count, 2);
if (gram in this.matchDict) { if (gram in this.match_dict) {
this.matchDict[gram].push([index, gramCount]); this.match_dict[gram].push([index, gram_count]);
} else { } else {
this.matchDict[gram] = [[index, gramCount]]; this.match_dict[gram] = [[index, gram_count]];
} }
} }
const vectorNormal = Math.sqrt(sumOfSquareGramCounts); const vector_normal = Math.sqrt(sum_of_square_gram_counts);
items[index] = [vectorNormal, normalizedValue]; items[index] = [vector_normal, normalized_value];
this.items[gramSize] = items; this.items[gram_size] = items;
this.exactSet[normalizedValue] = value; this.exact_set[normalized_value] = value;
}; };
get(value: string) { get(value: string) {
const normalizedValue = value.toLowerCase(); const normalized_value = value.toLowerCase();
const result = this.exactSet[normalizedValue]; const result = this.exact_set[normalized_value];
if (result) { if (result) {
return [[1, result]]; return [[1, result]];
@ -163,11 +157,11 @@ class FuzzySet {
let results = []; let results = [];
// start with high gram size and if there are no results, go to lower gram sizes // start with high gram size and if there are no results, go to lower gram sizes
for ( for (
let gramSize = GRAM_SIZE_UPPER; let gram_size = GRAM_SIZE_UPPER;
gramSize >= GRAM_SIZE_LOWER; gram_size >= GRAM_SIZE_LOWER;
--gramSize --gram_size
) { ) {
results = this.__get(value, gramSize); results = this.__get(value, gram_size);
if (results) { if (results) {
return results; return results;
} }
@ -175,68 +169,68 @@ class FuzzySet {
return null; return null;
} }
__get(value: string, gramSize: number) { __get(value: string, gram_size: number) {
const normalizedValue = value.toLowerCase(); const normalized_value = value.toLowerCase();
const matches = {}; const matches = {};
const gramCounts = _gramCounter(normalizedValue, gramSize); const gram_counts = gram_counter(normalized_value, gram_size);
const items = this.items[gramSize]; const items = this.items[gram_size];
let sumOfSquareGramCounts = 0; let sum_of_square_gram_counts = 0;
let gram; let gram;
let gramCount; let gram_count;
let i; let i;
let index; let index;
let otherGramCount; let other_gram_count;
for (gram in gramCounts) { for (gram in gram_counts) {
gramCount = gramCounts[gram]; gram_count = gram_counts[gram];
sumOfSquareGramCounts += Math.pow(gramCount, 2); sum_of_square_gram_counts += Math.pow(gram_count, 2);
if (gram in this.matchDict) { if (gram in this.match_dict) {
for (i = 0; i < this.matchDict[gram].length; ++i) { for (i = 0; i < this.match_dict[gram].length; ++i) {
index = this.matchDict[gram][i][0]; index = this.match_dict[gram][i][0];
otherGramCount = this.matchDict[gram][i][1]; other_gram_count = this.match_dict[gram][i][1];
if (index in matches) { if (index in matches) {
matches[index] += gramCount * otherGramCount; matches[index] += gram_count * other_gram_count;
} else { } else {
matches[index] = gramCount * otherGramCount; matches[index] = gram_count * other_gram_count;
} }
} }
} }
} }
const vectorNormal = Math.sqrt(sumOfSquareGramCounts); const vector_normal = Math.sqrt(sum_of_square_gram_counts);
let results = []; let results = [];
let matchScore; let match_score;
// build a results list of [score, str] // build a results list of [score, str]
for (const matchIndex in matches) { for (const matchIndex in matches) {
matchScore = matches[matchIndex]; match_score = matches[matchIndex];
results.push([ results.push([
matchScore / (vectorNormal * items[matchIndex][0]), match_score / (vector_normal * items[matchIndex][0]),
items[matchIndex][1], items[matchIndex][1],
]); ]);
} }
results.sort(sortDescending); results.sort(sort_descending);
let newResults = []; let new_results = [];
const endIndex = Math.min(50, results.length); const end_index = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50 // truncate somewhat arbitrarily to 50
for (let i = 0; i < endIndex; ++i) { for (let i = 0; i < end_index; ++i) {
newResults.push([ new_results.push([
_distance(results[i][1], normalizedValue), _distance(results[i][1], normalized_value),
results[i][1], results[i][1],
]); ]);
} }
results = newResults; results = new_results;
results.sort(sortDescending); results.sort(sort_descending);
newResults = []; new_results = [];
for (let i = 0; i < results.length; ++i) { for (let i = 0; i < results.length; ++i) {
if (results[i][0] == results[0][0]) { if (results[i][0] == results[0][0]) {
newResults.push([results[i][0], this.exactSet[results[i][1]]]); new_results.push([results[i][0], this.exact_set[results[i][1]]]);
} }
} }
return newResults; return new_results;
}; };
} }

@ -4,7 +4,7 @@ function tabsToSpaces(str: string) {
return str.replace(/^\t+/, match => match.split('\t').join(' ')); return str.replace(/^\t+/, match => match.split('\t').join(' '));
} }
export default function getCodeFrame( export default function get_code_frame(
source: string, source: string,
line: number, line: number,
column: number column: number

@ -1,12 +0,0 @@
import { Node } from '../interfaces';
export function get_tail_snippet(node: Node) {
const { start, end } = get_tail(node);
return `[✂${start}-${end}✂]`;
}
export function get_tail(node: Node) {
const end = node.end;
while (node.type === 'MemberExpression') node = node.object;
return { start: node.end, end };
}

@ -1,28 +0,0 @@
export default new Set([
'Array',
'Boolean',
'console',
'Date',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Infinity',
'Intl',
'isFinite',
'isNaN',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'parseFloat',
'parseInt',
'process',
'Promise',
'RegExp',
'Set',
'String',
'undefined',
]);

@ -1,8 +0,0 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export default function hash(str: string): string {
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36);
}

@ -1,5 +0,0 @@
const voidElementNames = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
export default function isVoidElementName(name: string) {
return voidElementNames.test(name) || name.toLowerCase() === '!doctype';
}

@ -1,18 +1,34 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn'; import { isIdentifierStart, isIdentifierChar } from 'acorn';
import full_char_code_at from './full_char_code_at'; import full_char_code_at from './full_char_code_at';
function is_valid(str: string): boolean { export const globals = new Set([
let i = 0; 'Array',
'Boolean',
while (i < str.length) { 'console',
const code = full_char_code_at(str, i); 'Date',
if (!(i === 0 ? isIdentifierStart : isIdentifierChar)(code, true)) return false; 'decodeURI',
'decodeURIComponent',
i += code <= 0xffff ? 1 : 2; 'encodeURI',
} 'encodeURIComponent',
'Infinity',
return true; 'Intl',
} 'isFinite',
'isNaN',
'JSON',
'Map',
'Math',
'NaN',
'Number',
'Object',
'parseFloat',
'parseInt',
'process',
'Promise',
'RegExp',
'Set',
'String',
'undefined',
]);
export const reserved = new Set([ export const reserved = new Set([
'arguments', 'arguments',
@ -65,12 +81,35 @@ export const reserved = new Set([
'yield', 'yield',
]); ]);
export function quote_name_if_necessary(name) { const void_element_names = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
export function is_void(name: string) {
return void_element_names.test(name) || name.toLowerCase() === '!doctype';
}
function is_valid(str: string): boolean {
let i = 0;
while (i < str.length) {
const code = full_char_code_at(str, i);
if (!(i === 0 ? isIdentifierStart : isIdentifierChar)(code, true)) return false;
i += code <= 0xffff ? 1 : 2;
}
return true;
}
export function quote_name_if_necessary(name: string) {
if (!is_valid(name)) return `"${name}"`; if (!is_valid(name)) return `"${name}"`;
return name; return name;
} }
export function quote_prop_if_necessary(name) { export function quote_prop_if_necessary(name: string) {
if (!is_valid(name)) return `["${name}"]`; if (!is_valid(name)) return `["${name}"]`;
return `.${name}`; return `.${name}`;
} }
export function sanitize(name: string) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}

@ -1,3 +0,0 @@
export default function(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
}

@ -1,36 +0,0 @@
import MagicString from 'magic-string';
import { Node } from '../interfaces';
export function removeNode(
code: MagicString,
start: number,
end: number,
body: Node,
node: Node
) {
const i = body.indexOf(node);
if (i === -1) throw new Error('node not in list');
let a;
let b;
if (body.length === 1) {
// remove everything, leave {}
a = start;
b = end;
} else if (i === 0) {
// remove everything before second node, including comments
a = start;
while (/\s/.test(code.original[a])) a += 1;
b = body[i].end;
while (/[\s,]/.test(code.original[b])) b += 1;
} else {
// remove the end of the previous node to the end of this one
a = body[i - 1].end;
b = node.end;
}
code.remove(a, b);
return;
}

@ -1,38 +0,0 @@
// asynchronous String#replace
export default async function replaceAsync(
str: string,
re: RegExp,
func: (...any) => Promise<string>
) {
const replacements: Promise<Replacement>[] = [];
str.replace(re, (...args) => {
replacements.push(
func(...args).then(
res =>
<Replacement>({
offset: args[args.length - 2],
length: args[0].length,
replacement: res,
})
)
);
return '';
});
let out = '';
let lastEnd = 0;
for (const { offset, length, replacement } of await Promise.all(
replacements
)) {
out += str.slice(lastEnd, offset) + replacement;
lastEnd = offset + length;
}
out += str.slice(lastEnd);
return out;
}
interface Replacement {
offset: number;
length: number;
replacement: string;
}

@ -1,3 +0,0 @@
export default function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
Loading…
Cancel
Save