Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore]: store regexp as variable instead of defining it inline #7716

Merged
merged 14 commits into from
Oct 13, 2022
22 changes: 14 additions & 8 deletions src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ interface ComponentOptions {
preserveWhitespace?: boolean;
}

const regex_leading_directory_separator = /^[/\\]/;
const regex_starts_with_term_export = /^Export/;
const regex_contains_term_function = /Function/;

export default class Component {
stats: Stats;
warnings: Warning[];
Expand Down Expand Up @@ -136,7 +140,7 @@ export default class Component {
(typeof process !== 'undefined'
? compile_options.filename
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
.replace(regex_leading_directory_separator, '')
: compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 });

Expand Down Expand Up @@ -638,7 +642,7 @@ export default class Component {
body.splice(i, 1);
}

if (/^Export/.test(node.type)) {
if (regex_starts_with_term_export.test(node.type)) {
const replacement = this.extract_exports(node, true);
if (replacement) {
body[i] = replacement;
Expand Down Expand Up @@ -795,7 +799,7 @@ export default class Component {
return this.skip();
}

if (/^Export/.test(node.type)) {
if (regex_starts_with_term_export.test(node.type)) {
const replacement = component.extract_exports(node);
if (replacement) {
this.replace(replacement);
Expand Down Expand Up @@ -918,7 +922,7 @@ export default class Component {
}

if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
return this.error(node as any, compiler_errors.contextual_store);
}
}
Expand Down Expand Up @@ -965,7 +969,7 @@ export default class Component {

walk(this.ast.instance.content, {
enter(node: Node) {
if (/Function/.test(node.type)) {
if (regex_contains_term_function.test(node.type)) {
return this.skip();
}

Expand Down Expand Up @@ -1089,7 +1093,7 @@ export default class Component {

this.replace(b`
${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${ props } } = $$props;`}
${ props.length > 0 && b`let { ${props} } = $$props;`}
${inserts}
` as any);
return this.skip();
Expand Down Expand Up @@ -1460,6 +1464,8 @@ export default class Component {
}
}

const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;

function process_component_options(component: Component, nodes) {
const component_options: ComponentOptions = {
immutable: component.compile_options.immutable || false,
Expand All @@ -1473,7 +1479,7 @@ function process_component_options(component: Component, nodes) {

const node = nodes.find(node => node.name === 'svelte:options');

function get_value(attribute, {code, message}) {
function get_value(attribute, { code, message }) {
const { value } = attribute;
const chunk = value[0];

Expand Down Expand Up @@ -1505,7 +1511,7 @@ function process_component_options(component: Component, nodes) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
}

if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
}

Expand Down
15 changes: 10 additions & 5 deletions src/compiler/compile/css/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import EachBlock from '../nodes/EachBlock';
import IfBlock from '../nodes/IfBlock';
import AwaitBlock from '../nodes/AwaitBlock';
import compiler_errors from '../compiler_errors';
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns';

enum BlockAppliesToNode {
NotPossible,
Expand All @@ -25,6 +26,8 @@ const whitelist_attribute_selector = new Map([
['dialog', new Set(['open'])]
]);

const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/;

export default class Selector {
node: CssNode;
stylesheet: Stylesheet;
Expand Down Expand Up @@ -157,7 +160,7 @@ export default class Selector {
for (const block of this.blocks) {
for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
if (/[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/.test(selector.children[0].value)) {
if (regex_is_single_css_selector.test(selector.children[0].value)) {
component.error(selector, compiler_errors.css_invalid_global_selector);
}
}
Expand Down Expand Up @@ -281,12 +284,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return true;
}

const regex_backslash_and_following_character = /\\(.)/g;

function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToNode {
let i = block.selectors.length;

while (i--) {
const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1');

if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return BlockAppliesToNode.NotPossible;
Expand Down Expand Up @@ -371,7 +376,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
const start_with_space = [];
const remaining = [];
current_possible_values.forEach((current_possible_value: string) => {
if (/^\s/.test(current_possible_value)) {
if (regex_starts_with_whitespace.test(current_possible_value)) {
start_with_space.push(current_possible_value);
} else {
remaining.push(current_possible_value);
Expand All @@ -392,7 +397,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
prev_values = combined;

start_with_space.forEach((value: string) => {
if (/\s$/.test(value)) {
if (regex_ends_with_whitespace.test(value)) {
possible_values.add(value);
} else {
prev_values.push(value);
Expand All @@ -406,7 +411,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
}

current_possible_values.forEach((current_possible_value: string) => {
if (/\s$/.test(current_possible_value)) {
if (regex_ends_with_whitespace.test(current_possible_value)) {
possible_values.add(current_possible_value);
} else {
prev_values.push(current_possible_value);
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/compile/css/Stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import hash from '../utils/hash';
import compiler_warnings from '../compiler_warnings';
import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore';
import { push_array } from '../../utils/push_array';
import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns';

const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;

function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
return name.replace(regex_css_browser_prefix, '');
}

const is_keyframes_node = (node: CssNode) =>
Expand Down Expand Up @@ -147,10 +150,10 @@ class Declaration {

// Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently
if (first.type === 'Raw' && /^\s+$/.test(first.value)) return;
if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return;

let start = first.start;
while (/\s/.test(code.original[start])) start += 1;
while (regex_whitespace.test(code.original[start])) start += 1;

if (start - c > 1) {
code.overwrite(c, start, ':');
Expand Down
7 changes: 5 additions & 2 deletions src/compiler/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const valid_options = [
'cssHash'
];

const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/;

function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace } = options;

Expand All @@ -48,11 +51,11 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
});

if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) {
if (name && !regex_valid_identifier.test(name)) {
throw new Error(`options.name must be a valid identifier (got '${name}')`);
}

if (name && /^[a-z]/.test(name)) {
if (name && regex_starts_with_lowercase_character.test(name)) {
const message = 'options.name should be capitalised';
warnings.push({
code: 'options-lowercase-name',
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/compile/nodes/Binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import get_object from '../utils/get_object';
import Expression from './shared/Expression';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import {dimensions} from '../../utils/patterns';
import { regex_dimensions } from '../../utils/patterns';
import { Node as ESTreeNode } from 'estree';
import { TemplateNode } from '../../interfaces';
import Element from './Element';
Expand Down Expand Up @@ -88,7 +88,7 @@ export default class Binding extends Node {
const type = parent.get_static_attribute_value('type');

this.is_readonly =
dimensions.test(this.name) ||
regex_dimensions.test(this.name) ||
(isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */);
Expand Down
26 changes: 17 additions & 9 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import StyleDirective from './StyleDirective';
import Text from './Text';
import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children';
import { dimensions, start_newline } from '../../utils/patterns';
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import Let from './Let';
Expand Down Expand Up @@ -203,6 +203,10 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st
}
}

const regex_any_repeated_whitespaces = /[\s]+/g;
const regex_heading_tags = /^h[1-6]$/;
const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;

export default class Element extends Node {
type: 'Element';
name: string;
Expand Down Expand Up @@ -253,7 +257,7 @@ export default class Element extends Node {
// places if there's another newline afterwards.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
first.data = first.data.replace(start_newline, '');
first.data = first.data.replace(regex_starts_with_newline, '');
}
}

Expand Down Expand Up @@ -398,7 +402,7 @@ export default class Element extends Node {

// Errors

if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) {
if (regex_illegal_attribute_character.test(name)) {
return component.error(attribute, compiler_errors.illegal_attribute(name));
}

Expand Down Expand Up @@ -464,7 +468,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match));
}

if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) {
component.warn(attribute, compiler_warnings.a11y_hidden(this.name));
}

Expand Down Expand Up @@ -729,7 +733,7 @@ export default class Element extends Node {
if (this.name === 'figure') {
const children = this.children.filter(node => {
if (node.type === 'Comment') return false;
if (node.type === 'Text') return /\S/.test(node.data);
if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true;
});

Expand Down Expand Up @@ -861,7 +865,7 @@ export default class Element extends Node {
if (this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name));
}
} else if (dimensions.test(name)) {
} else if (regex_dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`));
} else if (is_svg(this.name)) {
Expand Down Expand Up @@ -988,7 +992,7 @@ export default class Element extends Node {
if (attribute && !attribute.is_true) {
attribute.chunks.forEach((chunk, index) => {
if (chunk.type === 'Text') {
let data = chunk.data.replace(/[\s\n\t]+/g, ' ');
let data = chunk.data.replace(regex_any_repeated_whitespaces, ' ');
if (index === 0) {
data = data.trimLeft();
} else if (index === attribute.chunks.length - 1) {
Expand All @@ -1002,23 +1006,27 @@ export default class Element extends Node {
}
}

const regex_starts_with_vovel = /^[aeiou]/;

function should_have_attribute(
node,
attributes: string[],
name = node.name
) {
const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a';
const article = regex_starts_with_vovel.test(attributes[0]) ? 'an' : 'a';
const sequence = attributes.length > 1 ?
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0];

node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence));
}

const regex_minus_sign = /-/;

function within_custom_element(parent: INode) {
while (parent) {
if (parent.type === 'InlineComponent') return false;
if (parent.type === 'Element' && /-/.test(parent.name)) return true;
if (parent.type === 'Element' && regex_minus_sign.test(parent.name)) return true;
parent = parent.parent;
}
return false;
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/compile/nodes/EventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Identifier } from 'estree';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';

const regex_contains_term_function_expression = /FunctionExpression/;

export default class EventHandler extends Node {
type: 'EventHandler';
name: string;
Expand All @@ -25,7 +27,7 @@ export default class EventHandler extends Node {
this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context;

if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) {
if (regex_contains_term_function_expression.test(info.expression.type) && info.expression.params.length === 0) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive
this.can_make_passive = true;
Expand Down Expand Up @@ -55,7 +57,7 @@ export default class EventHandler extends Node {
}
const node = this.expression.node;

if (/FunctionExpression/.test(node.type)) {
if (regex_contains_term_function_expression.test(node.type)) {
return false;
}

Expand Down
3 changes: 2 additions & 1 deletion src/compiler/compile/nodes/Head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
import { regex_non_whitespace_character } from '../../utils/patterns';

export default class Head extends Node {
type: 'Head';
Expand All @@ -20,7 +21,7 @@ export default class Head extends Node {
}

this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || /\S/.test(child.data));
return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data));
}));

if (this.children.length > 0) {
Expand Down
Loading