Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-svelte3

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-svelte3 - npm Package Compare versions

Comparing version 3.0.0 to 3.1.0

4

CHANGELOG.md

@@ -0,1 +1,5 @@

# 3.1.0
- Add TypeScript support
# 3.0.0

@@ -2,0 +6,0 @@

@@ -103,2 +103,3 @@ 'use strict';

processor_options.named_blocks = settings['svelte3/named-blocks'];
processor_options.typescript = settings['svelte3/typescript'];
// call original Linter#verify

@@ -118,2 +119,279 @@ return verify.call(this, code, config, options);

var charToInteger = {};
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
for (var i = 0; i < chars.length; i++) {
charToInteger[chars.charCodeAt(i)] = i;
}
function decode(mappings) {
var decoded = [];
var line = [];
var segment = [
0,
0,
0,
0,
0,
];
var j = 0;
for (var i = 0, shift = 0, value = 0; i < mappings.length; i++) {
var c = mappings.charCodeAt(i);
if (c === 44) { // ","
segmentify(line, segment, j);
j = 0;
}
else if (c === 59) { // ";"
segmentify(line, segment, j);
j = 0;
decoded.push(line);
line = [];
segment[0] = 0;
}
else {
var integer = charToInteger[c];
if (integer === undefined) {
throw new Error('Invalid character (' + String.fromCharCode(c) + ')');
}
var hasContinuationBit = integer & 32;
integer &= 31;
value += integer << shift;
if (hasContinuationBit) {
shift += 5;
}
else {
var shouldNegate = value & 1;
value >>>= 1;
if (shouldNegate) {
value = value === 0 ? -0x80000000 : -value;
}
segment[j] += value;
j++;
value = shift = 0; // reset
}
}
}
segmentify(line, segment, j);
decoded.push(line);
return decoded;
}
function segmentify(line, segment, j) {
// This looks ugly, but we're creating specialized arrays with a specific
// length. This is much faster than creating a new array (which v8 expands to
// a capacity of 17 after pushing the first item), or slicing out a subarray
// (which is slow). Length 4 is assumed to be the most frequent, followed by
// length 5 (since not everything will have an associated name), followed by
// length 1 (it's probably rare for a source substring to not have an
// associated segment data).
if (j === 4)
line.push([segment[0], segment[1], segment[2], segment[3]]);
else if (j === 5)
line.push([segment[0], segment[1], segment[2], segment[3], segment[4]]);
else if (j === 1)
line.push([segment[0]]);
}
class GeneratedFragmentMapper {
constructor(generated_code, diff) {
this.generated_code = generated_code;
this.diff = diff;
}
get_position_relative_to_fragment(position_relative_to_file) {
const fragment_offset = this.offset_in_fragment(offset_at(position_relative_to_file, this.generated_code));
return position_at(fragment_offset, this.diff.generated_content);
}
offset_in_fragment(offset) {
return offset - this.diff.generated_start
}
}
class OriginalFragmentMapper {
constructor(original_code, diff) {
this.original_code = original_code;
this.diff = diff;
}
get_position_relative_to_file(position_relative_to_fragment) {
const parent_offset = this.offset_in_parent(offset_at(position_relative_to_fragment, this.diff.original_content));
return position_at(parent_offset, this.original_code);
}
offset_in_parent(offset) {
return this.diff.original_start + offset;
}
}
class SourceMapper {
constructor(raw_source_map) {
this.raw_source_map = raw_source_map;
}
get_original_position(generated_position) {
if (generated_position.line < 0) {
return { line: -1, column: -1 };
}
// Lazy-load
if (!this.decoded) {
this.decoded = decode(JSON.parse(this.raw_source_map).mappings);
}
let line = generated_position.line;
let column = generated_position.column;
let line_match = this.decoded[line];
while (line >= 0 && (!line_match || !line_match.length)) {
line -= 1;
line_match = this.decoded[line];
if (line_match && line_match.length) {
return {
line: line_match[line_match.length - 1][2],
column: line_match[line_match.length - 1][3]
};
}
}
if (line < 0) {
return { line: -1, column: -1 };
}
const column_match = line_match.find((col, idx) =>
idx + 1 === line_match.length ||
(col[0] <= column && line_match[idx + 1][0] > column)
);
return {
line: column_match[2],
column: column_match[3],
};
}
}
class DocumentMapper {
constructor(original_code, generated_code, diffs) {
this.original_code = original_code;
this.generated_code = generated_code;
this.diffs = diffs;
this.mappers = diffs.map(diff => {
return {
start: diff.generated_start,
end: diff.generated_end,
diff: diff.diff,
generated_fragment_mapper: new GeneratedFragmentMapper(generated_code, diff),
source_mapper: new SourceMapper(diff.map),
original_fragment_mapper: new OriginalFragmentMapper(original_code, diff)
}
});
}
get_original_position(generated_position) {
generated_position = { line: generated_position.line - 1, column: generated_position.column };
const offset = offset_at(generated_position, this.generated_code);
let original_offset = offset;
for (const mapper of this.mappers) {
if (offset >= mapper.start && offset <= mapper.end) {
return this.map(mapper, generated_position);
}
if (offset > mapper.end) {
original_offset -= mapper.diff;
}
}
const original_position = position_at(original_offset, this.original_code);
return this.to_ESLint_position(original_position);
}
map(mapper, generated_position) {
// Map the position to be relative to the transpiled fragment
const position_in_transpiled_fragment = mapper.generated_fragment_mapper.get_position_relative_to_fragment(
generated_position
);
// Map the position, using the sourcemap, to the original position in the source fragment
const position_in_original_fragment = mapper.source_mapper.get_original_position(
position_in_transpiled_fragment
);
// Map the position to be in the original fragment's parent
const original_position = mapper.original_fragment_mapper.get_position_relative_to_file(position_in_original_fragment);
return this.to_ESLint_position(original_position);
}
to_ESLint_position(position) {
// ESLint line/column is 1-based
return { line: position.line + 1, column: position.column + 1 };
}
}
/**
* Get the offset of the line and character position
* @param position Line and character position
* @param text The text for which the offset should be retrieved
*/
function offset_at(position, text) {
const line_offsets = get_line_offsets$1(text);
if (position.line >= line_offsets.length) {
return text.length;
} else if (position.line < 0) {
return 0;
}
const line_offset = line_offsets[position.line];
const next_line_offset =
position.line + 1 < line_offsets.length ? line_offsets[position.line + 1] : text.length;
return clamp(next_line_offset, line_offset, line_offset + position.column);
}
function position_at(offset, text) {
offset = clamp(offset, 0, text.length);
const line_offsets = get_line_offsets$1(text);
let low = 0;
let high = line_offsets.length;
if (high === 0) {
return { line: 0, column: offset };
}
while (low < high) {
const mid = Math.floor((low + high) / 2);
if (line_offsets[mid] > offset) {
high = mid;
} else {
low = mid + 1;
}
}
// low is the least x for which the line offset is larger than the current offset
// or array.length if no line offset is larger than the current offset
const line = low - 1;
return { line, column: offset - line_offsets[line] };
}
function get_line_offsets$1(text) {
const line_offsets = [];
let is_line_start = true;
for (let i = 0; i < text.length; i++) {
if (is_line_start) {
line_offsets.push(i);
is_line_start = false;
}
const ch = text.charAt(i);
is_line_start = ch === '\r' || ch === '\n';
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
i++;
}
}
if (is_line_start && text.length > 0) {
line_offsets.push(text.length);
}
return line_offsets;
}
function clamp(num, min, max) {
return Math.max(min, Math.min(max, num));
}
let default_compiler;

@@ -157,6 +435,7 @@

}
// get information about the component
let result;
try {
result = compiler.compile(text, { generate: false, ...processor_options.compiler_options });
result = compile_code(text, compiler, processor_options);
} catch ({ name, message, start, end }) {

@@ -177,3 +456,4 @@ // convert the error to a linting message, store it, and return

}
const { ast, warnings, vars } = result;
const { ast, warnings, vars, mapper } = 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')}}`;

@@ -183,18 +463,31 @@ state.var_names = new Set(vars.map(v => v.name));

// convert warnings to linting messages
state.messages = (processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings).map(({ code, message, start, end }) => ({
ruleId: code,
severity: 1,
message,
line: start && start.line,
column: start && start.column + 1,
endLine: end && end.line,
endColumn: end && end.column + 1,
}));
const filtered_warnings = processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings;
state.messages = filtered_warnings.map(({ code, message, start, end }) => {
const start_pos = processor_options.typescript && start ?
mapper.get_original_position(start) :
start && { line: start.line, column: start.column + 1 };
const end_pos = processor_options.typescript && end ?
mapper.get_original_position(end) :
end && { line: end.line, column: end.column + 1 };
return {
ruleId: code,
severity: 1,
message,
line: start_pos && start_pos.line,
column: start_pos && start_pos.column,
endLine: end_pos && end_pos.line,
endColumn: end_pos && end_pos.column,
};
});
// build strings that we can send along to ESLint to get the remaining messages
// Things to think about:
// - not all Svelte files may be typescript -> do we need a distinction on a file basis by analyzing the attribute + a config option to tell "treat all as TS"?
const with_file_ending = (filename) => `${filename}${processor_options.typescript ? '.ts' : '.js'}`;
if (ast.module) {
// block for <script context='module'>
const block = new_block();
state.blocks.set('module.js', block);
state.blocks.set(with_file_ending('module'), block);

@@ -213,3 +506,3 @@ get_translation(text, block, ast.module.content);

const block = new_block();
state.blocks.set('instance.js', block);
state.blocks.set(with_file_ending('instance'), block);

@@ -226,3 +519,3 @@ block.transformed_code = vars.filter(v => v.injected || v.module).map(v => `let ${v.name};`).join('');

const block = new_block();
state.blocks.set('template.js', block);
state.blocks.set(with_file_ending('template'), block);

@@ -280,2 +573,66 @@ block.transformed_code = vars.map(v => `let ${v.name};`).join('');

// How it works for JS:
// 1. compile code
// 2. return ast/vars/warnings
// How it works for TS:
// 1. transpile script contents from TS to JS
// 2. compile result to get Svelte compiler warnings
// 3. provide a mapper to map those warnings back to its original positions
// 4. blank script contents
// 5. compile again to get the AST with original positions in the markdown part
// 6. use AST and warnings of step 5, vars of step 2
function compile_code(text, compiler, processor_options) {
let ast;
let warnings;
let vars;
let mapper;
let ts_result;
if (processor_options.typescript) {
const diffs = [];
let accumulated_diff = 0;
const transpiled = text.replace(/<script(\s[^]*?)?>([^]*?)<\/script>/gi, (match, attributes = '', content) => {
const output = processor_options.typescript.transpileModule(
content,
{ reportDiagnostics: false, compilerOptions: { target: processor_options.typescript.ScriptTarget.ESNext, sourceMap: true } }
);
const original_start = text.indexOf(content);
const generated_start = accumulated_diff + original_start;
accumulated_diff += output.outputText.length - content.length;
diffs.push({
original_start: original_start,
generated_start: generated_start,
generated_end: generated_start + output.outputText.length,
diff: output.outputText.length - content.length,
original_content: content,
generated_content: output.outputText,
map: output.sourceMapText
});
return `<script${attributes}>${output.outputText}</script>`;
});
mapper = new DocumentMapper(text, transpiled, diffs);
ts_result = compiler.compile(transpiled, { generate: false, ...processor_options.compiler_options });
text = text.replace(/<script(\s[^]*?)?>([^]*?)<\/script>/gi, (match, attributes = '', content) => {
return `<script${attributes}>${content
// blank out the content
.replace(/[^\n]/g, ' ')
// excess blank space can make the svelte parser very slow (sec->min). break it up with comments (works in style/script)
.replace(/[^\n][^\n][^\n][^\n]\n/g, '/**/\n')
}</script>`;
});
}
const result = compiler.compile(text, { generate: false, ...processor_options.compiler_options });
if (!processor_options.typescript) {
({ ast, warnings, vars } = result);
} else {
ast = result.ast;
({ warnings, vars } = ts_result);
}
return { ast, warnings, vars, mapper };
}
// transform a linting message according to the module/instance script info we've gathered

@@ -282,0 +639,0 @@ const transform_message = ({ transformed_code }, { unoffsets, dedent, offsets, range }, message) => {

9

package.json
{
"name": "eslint-plugin-svelte3",
"version": "3.0.0",
"version": "3.1.0",
"description": "An ESLint plugin for Svelte v3 components.",

@@ -37,6 +37,11 @@ "keywords": [

"devDependencies": {
"@rollup/plugin-node-resolve": "^11.2.0",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"eslint": ">=6.0.0",
"rollup": "^2",
"svelte": "^3.2.0"
"sourcemap-codec": "1.4.8",
"svelte": "^3.2.0",
"typescript": "^4.0.0"
}
}

@@ -59,2 +59,48 @@ # eslint-plugin-svelte3

### Installation with TypeScript
If you want to use TypeScript, you'll need a different ESLint configuration. In addition to the Svelte plugin, you also need the ESLint TypeScript parser and plugin. Install `typescript`, `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin` from npm and then adjust your config like this:
```javascript
module.exports = {
parser: '@typescript-eslint/parser', // add the TypeScript parser
plugins: [
'svelte3',
'@typescript-eslint' // add the TypeScript plugin
],
overrides: [ // this stays the same
{
files: ['*.svelte'],
processor: 'svelte3/svelte3'
}
],
rules: {
// ...
},
settings: {
'svelte3/typescript': require('typescript'), // pass the TypeScript package to the Svelte plugin
// ...
}
};
```
If you also want to be able to use type-aware linting rules (which will result in slower linting, because the whole program needs to be compiled and type-checked), then you also need to add some `parserOptions` configuration. The values below assume that your ESLint config is at the root of your project next to your `tsconfig.json`. For more information, see [here](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md).
```javascript
module.exports = {
// ...
parserOptions: { // add these parser options
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
},
extends: [ // then, enable whichever type-aware rules you want to use
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
// ...
};
```
## Interactions with other plugins

@@ -94,3 +140,3 @@

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. When this setting is enabled, 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`.
When an [ESLint processor](https://eslint.org/docs/user-guide/configuring/plugins#specifying-processor) processes a file, it is able to output named code blocks, which can each have their own linting configuration. When this setting is enabled, 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`.

@@ -101,2 +147,8 @@ This means that to override linting rules in Svelte components, you'd instead have to target `**/*.svelte/*.js`. But it also 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.

### `svelte3/typescript`
If you use TypeScript inside your Svelte components and want ESLint support, you need to set this option. It expects an instance of the TypeScript package. This probably means doing `'svelte3/typescript': require('typescript')`.
The default is to not enable TypeScript support.
### `svelte3/compiler`

@@ -103,0 +155,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc