eslint-plugin-svelte3
Advanced tools
Comparing version 2.6.0 to 2.7.0
@@ -0,1 +1,5 @@ | ||
# 2.7.0 | ||
- Expose the parts of each linted component as separate named code blocks `module.js`, `instance.js`, and `template.js` | ||
# 2.6.0 | ||
@@ -2,0 +6,0 @@ |
226
index.js
'use strict'; | ||
const SCRIPT = 1, TEMPLATE_QUOTED = 2, TEMPLATE_UNQUOTED = 3; | ||
let compiler, default_compiler, compiler_options, messages, transformed_code, line_offsets, ignore_warnings, ignore_styles, translations, var_names; | ||
const get_compiler = () => compiler || default_compiler || (default_compiler = require('svelte/compiler')); | ||
const blocks = new Map(); | ||
const new_block = () => ({ transformed_code: '', line_offsets: null, translations: new Map() }); | ||
let custom_compiler, default_compiler, compiler_options, messages, ignore_warnings, ignore_styles, var_names; | ||
@@ -56,3 +56,3 @@ // get the total length, number of lines, and length of the last line of a string | ||
// transform a linting message according to the module/instance script info we've gathered | ||
const transform_message = (message, { unoffsets, dedent, offsets, range }) => { | ||
const transform_message = ({ transformed_code }, { unoffsets, dedent, offsets, range }, message) => { | ||
// strip out the start and end of the fix if they are not actually changes | ||
@@ -130,5 +130,21 @@ if (message.fix) { | ||
// get translation info and include the processed scripts in this block's transformed_code | ||
const get_translation = (text, block, node, options = {}) => { | ||
block.transformed_code += '\n'; | ||
const translation = { options, unoffsets: get_offsets(block.transformed_code) }; | ||
translation.range = [node.start, node.end]; | ||
const { dedented, offsets } = dedent_code(text.slice(node.start, node.end)); | ||
block.transformed_code += dedented; | ||
translation.offsets = get_offsets(text.slice(0, node.start)); | ||
translation.dedent = offsets; | ||
const end = get_offsets(block.transformed_code).lines; | ||
for (let i = translation.unoffsets.lines; i <= end; i++) { | ||
block.translations.set(i, translation); | ||
} | ||
block.transformed_code += '\n'; | ||
}; | ||
// find the contextual name or names described by a particular node in the AST | ||
const contextual_names = []; | ||
const find_contextual_names = node => { | ||
const find_contextual_names = (compiler, node) => { | ||
if (node) { | ||
@@ -138,3 +154,3 @@ if (typeof node === 'string') { | ||
} else if (typeof node === 'object') { | ||
get_compiler().walk(node, { | ||
compiler.walk(node, { | ||
enter(node, parent, prop) { | ||
@@ -152,2 +168,3 @@ if (node.name && prop !== 'key') { | ||
const preprocess = text => { | ||
const compiler = custom_compiler || default_compiler || (default_compiler = require('svelte/compiler')); | ||
if (ignore_styles) { | ||
@@ -171,3 +188,3 @@ // wipe the appropriate <style> tags in the file | ||
try { | ||
result = get_compiler().compile(text, { generate: false, ...compiler_options }); | ||
result = compiler.compile(text, { generate: false, ...compiler_options }); | ||
} catch ({ name, message, start, end }) { | ||
@@ -189,6 +206,4 @@ // convert the error to a linting message, store it, and return | ||
const { ast, warnings, vars } = result; | ||
const references_and_reassignments = `{${vars.filter(v => v.referenced).map(v => v.name)};${vars.filter(v => v.reassigned || v.export_name).map(v => v.name + '=0')}}`; | ||
var_names = new Set(vars.map(v => v.name)); | ||
const injected_vars = vars.filter(v => v.injected); | ||
const referenced_vars = vars.filter(v => v.referenced); | ||
const reassigned_vars = vars.filter(v => v.reassigned || v.export_name); | ||
@@ -206,116 +221,112 @@ // convert warnings to linting messages | ||
// build a string that we can send along to ESLint to get the remaining messages | ||
// build strings that we can send along to ESLint to get the remaining messages | ||
// include declarations of all injected identifiers | ||
transformed_code = injected_vars.length ? `let ${injected_vars.map(v => v.name).join(',')};\n` : ''; | ||
if (ast.module) { | ||
// block for <script context='module'> | ||
const block = new_block(); | ||
blocks.set('module.js', block); | ||
// get translation info and include the processed scripts in transformed_code | ||
const get_translation = (node, type) => { | ||
const translation = { type, unoffsets: get_offsets(transformed_code) }; | ||
translation.range = [node.start, node.end]; | ||
const { dedented, offsets } = dedent_code(text.slice(node.start, node.end)); | ||
transformed_code += dedented; | ||
translation.offsets = get_offsets(text.slice(0, node.start)); | ||
translation.dedent = offsets; | ||
const end = get_offsets(transformed_code).lines; | ||
for (let i = translation.unoffsets.lines; i <= end; i++) { | ||
translations.set(i, translation); | ||
get_translation(text, block, ast.module.content); | ||
if (ast.instance) { | ||
block.transformed_code += text.slice(ast.instance.content.start, ast.instance.content.end); | ||
} | ||
}; | ||
translations = new Map(); | ||
if (ast.module) { | ||
get_translation(ast.module.content, SCRIPT); | ||
block.transformed_code += references_and_reassignments; | ||
} | ||
transformed_code += '\n'; | ||
if (ast.instance) { | ||
get_translation(ast.instance.content, SCRIPT); | ||
} | ||
transformed_code += '\n'; | ||
// block for <script context='instance'> | ||
const block = new_block(); | ||
blocks.set('instance.js', block); | ||
// no-unused-vars: create references to all identifiers referred to by the template | ||
if (referenced_vars.length) { | ||
transformed_code += `{${referenced_vars.map(v => v.name).join(';')}}\n`; | ||
block.transformed_code = vars.filter(v => v.injected || v.module).map(v => `let ${v.name};`).join(''); | ||
get_translation(text, block, ast.instance.content); | ||
block.transformed_code += references_and_reassignments; | ||
} | ||
// prefer-const: create reassignments for all vars reassigned in component and for all exports | ||
if (reassigned_vars.length) { | ||
transformed_code += `{${reassigned_vars.map(v => v.name + '=0').join(';')}}\n`; | ||
if (ast.html) { | ||
// block for template | ||
const block = new_block(); | ||
blocks.set('template.js', block); | ||
block.transformed_code = vars.map(v => `let ${v.name};`).join(''); | ||
const nodes_with_contextual_scope = new WeakSet(); | ||
let in_quoted_attribute = false; | ||
compiler.walk(ast.html, { | ||
enter(node, parent, prop) { | ||
if (prop === 'expression') { | ||
return this.skip(); | ||
} else if (prop === 'attributes' && '\'"'.includes(text[node.end - 1])) { | ||
in_quoted_attribute = true; | ||
} | ||
contextual_names.length = 0; | ||
find_contextual_names(compiler, node.context); | ||
if (node.type === 'EachBlock') { | ||
find_contextual_names(compiler, node.index); | ||
} else if (node.type === 'ThenBlock') { | ||
find_contextual_names(compiler, parent.value); | ||
} else if (node.type === 'CatchBlock') { | ||
find_contextual_names(compiler, parent.error); | ||
} else if (node.type === 'Element' || node.type === 'InlineComponent') { | ||
node.attributes.forEach(node => node.type === 'Let' && find_contextual_names(compiler, node.expression || node.name)); | ||
} | ||
if (contextual_names.length) { | ||
nodes_with_contextual_scope.add(node); | ||
block.transformed_code += `{let ${contextual_names.map(name => `${name}=0`).join(',')};`; | ||
} | ||
if (node.expression && typeof node.expression === 'object') { | ||
// add the expression in question to the constructed string | ||
block.transformed_code += '('; | ||
get_translation(text, block, node.expression, { template: true, in_quoted_attribute }); | ||
block.transformed_code += ');'; | ||
} | ||
}, | ||
leave(node, parent, prop) { | ||
if (prop === 'attributes') { | ||
in_quoted_attribute = false; | ||
} | ||
// close contextual scope | ||
if (nodes_with_contextual_scope.has(node)) { | ||
block.transformed_code += '}'; | ||
} | ||
}, | ||
}); | ||
} | ||
// add expressions from template to the constructed string | ||
const nodes_with_contextual_scope = new WeakSet(); | ||
let in_quoted_attribute = false; | ||
get_compiler().walk(ast.html, { | ||
enter(node, parent, prop) { | ||
if (prop === 'expression') { | ||
return this.skip(); | ||
} else if (prop === 'attributes' && '\'"'.includes(text[node.end - 1])) { | ||
in_quoted_attribute = true; | ||
} | ||
contextual_names.length = 0; | ||
find_contextual_names(node.context); | ||
if (node.type === 'EachBlock') { | ||
find_contextual_names(node.index); | ||
} else if (node.type === 'ThenBlock') { | ||
find_contextual_names(parent.value); | ||
} else if (node.type === 'CatchBlock') { | ||
find_contextual_names(parent.error); | ||
} else if (node.type === 'Element' || node.type === 'InlineComponent') { | ||
node.attributes.forEach(node => node.type === 'Let' && find_contextual_names(node.expression || node.name)); | ||
} | ||
if (contextual_names.length) { | ||
nodes_with_contextual_scope.add(node); | ||
transformed_code += `{let ${contextual_names.map(name => `${name}=0`).join(',')};`; | ||
} | ||
if (node.expression && typeof node.expression === 'object') { | ||
// add the expression in question to the constructed string | ||
transformed_code += '(\n'; | ||
get_translation(node.expression, in_quoted_attribute ? TEMPLATE_QUOTED : TEMPLATE_UNQUOTED); | ||
transformed_code += '\n);'; | ||
} | ||
}, | ||
leave(node, parent, prop) { | ||
if (prop === 'attributes') { | ||
in_quoted_attribute = false; | ||
} | ||
// close contextual scope | ||
if (nodes_with_contextual_scope.has(node)) { | ||
transformed_code += '}'; | ||
} | ||
}, | ||
}); | ||
// return processed string | ||
return [transformed_code]; | ||
return [...blocks].map(([filename, { transformed_code: text }]) => ({ text, filename })); | ||
}; | ||
// extract the string referenced by a message | ||
const get_referenced_string = message => { | ||
const get_referenced_string = (block, message) => { | ||
if (message.line && message.column && message.endLine && message.endColumn) { | ||
if (!line_offsets) { | ||
line_offsets = [-1, -1]; | ||
for (let i = 0; i < transformed_code.length; i++) { | ||
if (transformed_code[i] === '\n') { | ||
line_offsets.push(i); | ||
if (!block.line_offsets) { | ||
block.line_offsets = [-1, -1]; | ||
for (let i = 0; i < block.transformed_code.length; i++) { | ||
if (block.transformed_code[i] === '\n') { | ||
block.line_offsets.push(i); | ||
} | ||
} | ||
} | ||
return transformed_code.slice(line_offsets[message.line] + message.column, line_offsets[message.endLine] + message.endColumn); | ||
return block.transformed_code.slice(block.line_offsets[message.line] + message.column, block.line_offsets[message.endLine] + message.endColumn); | ||
} | ||
}; | ||
// extract something that looks like an identifier (minus unicode escape stuff) from the beginning of a string | ||
// extract something that looks like an identifier (not supporting unicode escape stuff) from the beginning of a string | ||
const get_identifier = str => (str && str.match(/^[^\s!"#%&\\'()*+,\-./:;<=>?@[\\\]^`{|}~]+/) || [])[0]; | ||
// determine whether this message from ESLint is something we care about | ||
const is_valid_message = (message, { type }) => { | ||
const is_valid_message = (block, message, { options }) => { | ||
switch (message.ruleId) { | ||
case 'eol-last': return false; | ||
case 'indent': return type === SCRIPT; | ||
case 'no-labels': return get_identifier(get_referenced_string(message)) !== '$'; | ||
case 'no-restricted-syntax': return message.nodeType !== 'LabeledStatement' || get_identifier(get_referenced_string(message)) !== '$'; | ||
case 'no-self-assign': return !var_names.has(get_identifier(get_referenced_string(message))); | ||
case 'no-unused-labels': return get_referenced_string(message) !== '$'; | ||
case 'quotes': return type !== TEMPLATE_QUOTED; | ||
case 'indent': return !options.template; | ||
case 'no-labels': return get_identifier(get_referenced_string(block, message)) !== '$'; | ||
case 'no-restricted-syntax': return message.nodeType !== 'LabeledStatement' || get_identifier(get_referenced_string(block, message)) !== '$'; | ||
case 'no-self-assign': return !var_names.has(get_identifier(get_referenced_string(block, message))); | ||
case 'no-unused-labels': return get_referenced_string(block, message) !== '$'; | ||
case 'quotes': return !options.in_quoted_attribute; | ||
} | ||
@@ -326,10 +337,12 @@ return true; | ||
// transform linting messages and combine with compiler warnings | ||
const postprocess = ([raw_messages]) => { | ||
const postprocess = blocks_messages => { | ||
// filter messages and fix their offsets | ||
if (raw_messages) { | ||
for (let i = 0; i < raw_messages.length; i++) { | ||
const message = raw_messages[i]; | ||
const translation = translations.get(message.line); | ||
if (translation && is_valid_message(message, translation)) { | ||
transform_message(message, translation); | ||
const blocks_array = [...blocks.values()]; | ||
for (let i = 0; i < blocks_messages.length; i++) { | ||
const block = blocks_array[i]; | ||
for (let j = 0; j < blocks_messages[i].length; j++) { | ||
const message = blocks_messages[i][j]; | ||
const translation = block.translations.get(message.line); | ||
if (translation && is_valid_message(block, message, translation)) { | ||
transform_message(block, translation, message); | ||
messages.push(message); | ||
@@ -342,3 +355,4 @@ } | ||
const sorted_messages = messages.sort((a, b) => a.line - b.line || a.column - b.column); | ||
compiler_options = messages = transformed_code = line_offsets = ignore_warnings = ignore_styles = translations = var_names = null; | ||
blocks.clear(); | ||
custom_compiler = ignore_warnings = ignore_styles = compiler_options = messages = var_names = null; | ||
return sorted_messages; | ||
@@ -361,3 +375,3 @@ }; | ||
const settings = config && (typeof config.extractConfig === 'function' ? config.extractConfig(options.filename) : config).settings || {}; | ||
compiler = settings['svelte3/compiler']; | ||
custom_compiler = settings['svelte3/compiler']; | ||
ignore_warnings = settings['svelte3/ignore-warnings']; | ||
@@ -364,0 +378,0 @@ ignore_styles = settings['svelte3/ignore-styles']; |
{ | ||
"name": "eslint-plugin-svelte3", | ||
"version": "2.6.0", | ||
"version": "2.7.0", | ||
"description": "An ESLint plugin for Svelte v3 components.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -44,3 +44,3 @@ # eslint-plugin-svelte3 | ||
{ | ||
files: ['*.svelte'], | ||
files: ['**/*.svelte'], | ||
processor: 'svelte3/svelte3' | ||
@@ -100,2 +100,6 @@ } | ||
## Named code blocks | ||
When an [ESLint processor](https://eslint.org/docs/user-guide/configuring#specifying-processor) processes a file, it is able to output named code blocks, which can each have their own linting configuration. In this plugin, the code extracted from `<script context='module>` tag, the `<script>` tag, and the template are respectively given the block names `module.js`, `instance.js`, and `template.js`. This means that you can define an override targeting `**/*.svelte/*_template.js` for example, and that configuration will only apply to linting done on the templates in Svelte components. | ||
## Using the CLI | ||
@@ -102,0 +106,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23941
345
116