Add eslint custom rules for tabs and trailing spaces (#2667)
`wpt lint` doesn't like tabs or trailing spaces, even inside multiline strings (which eslint normally ignores). Add custom rules to catch these. Issue: 2628
This commit is contained in:
parent
1dd8ad4f79
commit
0be3bde33c
19 changed files with 152 additions and 57 deletions
|
|
@ -12,7 +12,7 @@
|
|||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": ["node", "ban", "import", "deprecation"],
|
||||
"plugins": ["node", "ban", "import", "deprecation", "gpuweb-cts"],
|
||||
"rules": {
|
||||
// Core rules
|
||||
"linebreak-style": ["warn", "unix"],
|
||||
|
|
@ -27,6 +27,10 @@
|
|||
// TODOs not relating to test coverage can be marked MAINTENANCE_TODO or similar.
|
||||
"no-warning-comments": ["warn", { "terms": ["todo", "fixme", "xxx"], "location": "anywhere" }],
|
||||
|
||||
// Plugin: gpuweb-cts
|
||||
"gpuweb-cts/string-trailing-space": "warn",
|
||||
"gpuweb-cts/string-tabs": "warn",
|
||||
|
||||
// Plugin: @typescript-eslint
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "warn",
|
||||
|
|
|
|||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -31,6 +31,7 @@
|
|||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-ban": "^1.6.0",
|
||||
"eslint-plugin-deprecation": "^1.3.3",
|
||||
"eslint-plugin-gpuweb-cts": "file:./tools/eslint-plugin-gpuweb-cts",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"express": "^4.18.2",
|
||||
"grunt": "^1.5.3",
|
||||
|
|
@ -2622,6 +2623,10 @@
|
|||
"eslint": ">=4.19.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-gpuweb-cts": {
|
||||
"resolved": "tools/eslint-plugin-gpuweb-cts",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/eslint-plugin-import": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
|
||||
|
|
@ -8984,6 +8989,16 @@
|
|||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"tools/eslint-custom-rules": {
|
||||
"version": "0.0.0",
|
||||
"extraneous": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"tools/eslint-plugin-gpuweb-cts": {
|
||||
"version": "0.0.0",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -10993,6 +11008,9 @@
|
|||
"regexpp": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-gpuweb-cts": {
|
||||
"version": "file:tools/eslint-plugin-gpuweb-cts"
|
||||
},
|
||||
"eslint-plugin-import": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/gpuweb/cts#readme",
|
||||
"devDependencies": {
|
||||
"eslint-plugin-gpuweb-cts": "file:./tools/eslint-plugin-gpuweb-cts",
|
||||
"@babel/cli": "^7.19.3",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
|
|
|
|||
|
|
@ -88,13 +88,13 @@ g.test('basic')
|
|||
override c8: u32; // type: uint32
|
||||
override c9: u32 = 0u; // default override
|
||||
override c10: u32 = 10u; // default
|
||||
|
||||
|
||||
struct Buf {
|
||||
data : array<u32, ${count}>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main() {
|
||||
buf.data[0] = u32(c0);
|
||||
buf.data[1] = u32(c1);
|
||||
|
|
@ -130,13 +130,13 @@ g.test('numeric_id')
|
|||
@id(1001) override c1: u32; // some big numeric id
|
||||
@id(1) override c2: u32 = 0u; // id == 1 might collide with some generated constant id
|
||||
@id(1003) override c3: u32 = 3u; // default
|
||||
|
||||
|
||||
struct Buf {
|
||||
data : array<u32, 3>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main() {
|
||||
buf.data[0] = c1;
|
||||
buf.data[1] = c2;
|
||||
|
|
@ -165,13 +165,13 @@ g.test('precision')
|
|||
`
|
||||
override c1: f32;
|
||||
override c2: f32;
|
||||
|
||||
|
||||
struct Buf {
|
||||
data : array<f32, 2>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main() {
|
||||
buf.data[0] = c1;
|
||||
buf.data[1] = c2;
|
||||
|
|
@ -206,9 +206,9 @@ g.test('workgroup_size')
|
|||
struct Buf {
|
||||
data : array<u32, 1>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(${workgroup_size_str}) fn main(
|
||||
@builtin(local_invocation_id) local_invocation_id : vec3<u32>
|
||||
) {
|
||||
|
|
@ -233,9 +233,9 @@ g.test('shared_shader_module')
|
|||
struct Buf {
|
||||
data : array<u32, 1>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main() {
|
||||
buf.data[0] = a;
|
||||
}`,
|
||||
|
|
@ -334,21 +334,21 @@ g.test('multi_entry_points')
|
|||
override c1: u32;
|
||||
override c2: u32;
|
||||
override c3: u32;
|
||||
|
||||
|
||||
struct Buf {
|
||||
data : array<u32, 1>
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0) var<storage, read_write> buf : Buf;
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main1() {
|
||||
buf.data[0] = c1;
|
||||
}
|
||||
|
||||
|
||||
@compute @workgroup_size(1) fn main2() {
|
||||
buf.data[0] = c2;
|
||||
}
|
||||
|
||||
|
||||
@compute @workgroup_size(c3) fn main3() {
|
||||
buf.data[0] = 3u;
|
||||
}`,
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ g.test('object_has_descriptor_label')
|
|||
.desc(
|
||||
`
|
||||
For every create function, the descriptor.label is carried over to the object.label.
|
||||
|
||||
|
||||
TODO: test importExternalTexture
|
||||
TODO: make a best effort and generating an error that is likely to use label. There's nothing to check for
|
||||
but it may surface bugs related to unusual labels.
|
||||
|
|
|
|||
|
|
@ -420,7 +420,7 @@ g.test('multi_entry_points')
|
|||
override B: f32 = 3.0;
|
||||
override C: f32;
|
||||
override D: f32;
|
||||
|
||||
|
||||
@vertex fn vertexMain(
|
||||
@builtin(vertex_index) VertexIndex : u32
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ g.test('createBindGroupLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createBindGroupLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -68,7 +68,7 @@ g.test('createPipelineLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createPipelineLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -102,7 +102,7 @@ g.test('createPipeline,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ g.test('createBindGroupLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createBindGroupLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -68,7 +68,7 @@ g.test('createPipelineLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createPipelineLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -102,7 +102,7 @@ g.test('createPipeline,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ g.test('createBindGroupLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createBindGroupLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -68,7 +68,7 @@ g.test('createPipelineLayout,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using at and over ${limit} limit in createPipelineLayout
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
@ -102,7 +102,7 @@ g.test('createPipeline,at_over')
|
|||
.desc(
|
||||
`
|
||||
Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
|
||||
|
||||
|
||||
Note: We also test order to make sure the implementation isn't just looking
|
||||
at just the last entry.
|
||||
`
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ g.test('sample_count,1d_2d_array_3d')
|
|||
g.test('texture_size,default_value_and_smallest_size,uncompressed_format')
|
||||
.desc(
|
||||
`Test default values for height and depthOrArrayLayers for every dimension type and every uncompressed format.
|
||||
It also tests smallest size (lower bound) for every dimension type and every uncompressed format, while other texture_size tests are testing the upper bound.`
|
||||
It also tests smallest size (lower bound) for every dimension type and every uncompressed format, while other texture_size tests are testing the upper bound.`
|
||||
)
|
||||
.params(u =>
|
||||
u
|
||||
|
|
@ -443,7 +443,7 @@ g.test('texture_size,default_value_and_smallest_size,uncompressed_format')
|
|||
g.test('texture_size,default_value_and_smallest_size,compressed_format')
|
||||
.desc(
|
||||
`Test default values for height and depthOrArrayLayers for every dimension type and every compressed format.
|
||||
It also tests smallest size (lower bound) for every dimension type and every compressed format, while other texture_size tests are testing the upper bound.`
|
||||
It also tests smallest size (lower bound) for every dimension type and every compressed format, while other texture_size tests are testing the upper bound.`
|
||||
)
|
||||
.params(u =>
|
||||
u
|
||||
|
|
|
|||
|
|
@ -2,31 +2,31 @@ writeTexture + copyBufferToTexture + copyTextureToBuffer validation tests.
|
|||
|
||||
Test coverage:
|
||||
* resource usages:
|
||||
- texture_usage_must_be_valid: for GPUTextureUsage::COPY_SRC, GPUTextureUsage::COPY_DST flags.
|
||||
- buffer_usage_must_be_valid: for GPUBufferUsage::COPY_SRC, GPUBufferUsage::COPY_DST flags.
|
||||
- texture_usage_must_be_valid: for GPUTextureUsage::COPY_SRC, GPUTextureUsage::COPY_DST flags.
|
||||
- buffer_usage_must_be_valid: for GPUBufferUsage::COPY_SRC, GPUBufferUsage::COPY_DST flags.
|
||||
|
||||
* textureCopyView:
|
||||
- texture_must_be_valid: for valid, destroyed, error textures.
|
||||
- sample_count_must_be_1: for sample count 1 and 4.
|
||||
- mip_level_must_be_in_range: for various combinations of mipLevel and mipLevelCount.
|
||||
- format: for all formats with full and non-full copies on width, height, and depth.
|
||||
- texel_block_alignment_on_origin: for all formats and coordinates.
|
||||
- texture_must_be_valid: for valid, destroyed, error textures.
|
||||
- sample_count_must_be_1: for sample count 1 and 4.
|
||||
- mip_level_must_be_in_range: for various combinations of mipLevel and mipLevelCount.
|
||||
- format: for all formats with full and non-full copies on width, height, and depth.
|
||||
- texel_block_alignment_on_origin: for all formats and coordinates.
|
||||
|
||||
* bufferCopyView:
|
||||
- buffer_must_be_valid: for valid, destroyed, error buffers.
|
||||
- bytes_per_row_alignment: for bytesPerRow to be 256-byte aligned or not, and bytesPerRow is required or not.
|
||||
- buffer_must_be_valid: for valid, destroyed, error buffers.
|
||||
- bytes_per_row_alignment: for bytesPerRow to be 256-byte aligned or not, and bytesPerRow is required or not.
|
||||
|
||||
* linear texture data:
|
||||
- bound_on_rows_per_image: for various combinations of copyDepth (1, >1), copyHeight, rowsPerImage.
|
||||
- offset_plus_required_bytes_in_copy_overflow
|
||||
- required_bytes_in_copy: testing minimal data size and data size too small for various combinations of bytesPerRow, rowsPerImage, copyExtent and offset. for the copy method, bytesPerRow is computed as bytesInACompleteRow aligned to be a multiple of 256 + bytesPerRowPadding * 256.
|
||||
- texel_block_alignment_on_rows_per_image: for all formats.
|
||||
- offset_alignment: for all formats.
|
||||
- bound_on_offset: for various combinations of offset and dataSize.
|
||||
- bound_on_rows_per_image: for various combinations of copyDepth (1, >1), copyHeight, rowsPerImage.
|
||||
- offset_plus_required_bytes_in_copy_overflow
|
||||
- required_bytes_in_copy: testing minimal data size and data size too small for various combinations of bytesPerRow, rowsPerImage, copyExtent and offset. for the copy method, bytesPerRow is computed as bytesInACompleteRow aligned to be a multiple of 256 + bytesPerRowPadding * 256.
|
||||
- texel_block_alignment_on_rows_per_image: for all formats.
|
||||
- offset_alignment: for all formats.
|
||||
- bound_on_offset: for various combinations of offset and dataSize.
|
||||
|
||||
* texture copy range:
|
||||
- 1d_texture: copyExtent.height isn't 1, copyExtent.depthOrArrayLayers isn't 1.
|
||||
- texel_block_alignment_on_size: for all formats and coordinates.
|
||||
- texture_range_conditons: for all coordinate and various combinations of origin, copyExtent, textureSize and mipLevel.
|
||||
- 1d_texture: copyExtent.height isn't 1, copyExtent.depthOrArrayLayers isn't 1.
|
||||
- texel_block_alignment_on_size: for all formats and coordinates.
|
||||
- texture_range_conditons: for all coordinate and various combinations of origin, copyExtent, textureSize and mipLevel.
|
||||
|
||||
TODO: more test coverage for 1D and 3D textures.
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export function runStorageVariableTest({
|
|||
const wgsl = `
|
||||
@group(0) @binding(0)
|
||||
var<storage, read_write> output: array<atomic<${scalarType}>>;
|
||||
|
||||
|
||||
@compute @workgroup_size(${workgroupSize})
|
||||
fn main(
|
||||
@builtin(global_invocation_id) global_invocation_id : vec3<u32>,
|
||||
|
|
@ -135,7 +135,7 @@ export function runWorkgroupVariableTest({
|
|||
// Result of each workgroup is written to output[workgroup_id.x]
|
||||
@group(0) @binding(0)
|
||||
var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
|
||||
|
||||
|
||||
@compute @workgroup_size(${workgroupSize})
|
||||
fn main(
|
||||
@builtin(local_invocation_index) local_invocation_index: u32,
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ g.test('corw2')
|
|||
.desc(
|
||||
`The first thread reads from some memory location x, and then writes 1 to x. The second thread
|
||||
writes 2 to x. If the first thread reads the value 2, but the value in memory after the test
|
||||
completes is 1, then the instructions on the first thread have been re-ordered, leading to a
|
||||
completes is 1, then the instructions on the first thread have been re-ordered, leading to a
|
||||
coherence violation.
|
||||
`
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@
|
|||
draw(cvs0.getContext('2d'));
|
||||
draw(cvs1.getContext('2d'));
|
||||
draw(cvs2.getContext('2d'));
|
||||
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
--testcase-log-even-bg-color: #080808;
|
||||
--testcase-log-text-fg-color: #aaa;
|
||||
--testcase-log-text-first-line-fg-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
body {
|
||||
font-family: monospace;
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
background: var(--testcase-log-even-bg-color);
|
||||
}
|
||||
.testcaselogbtn::before {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMAQMAAABsu86kAAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAACRJREFUCNdjYGBg+H+AwUGBwV+BQUGAQX0CiNQQYFABk8ogLgBsYQUt2gNKPwAAAABJRU5ErkJggg==);
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMAQMAAABsu86kAAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAACRJREFUCNdjYGBg+H+AwUGBwV+BQUGAQX0CiNQQYFABk8ogLgBsYQUt2gNKPwAAAABJRU5ErkJggg==);
|
||||
}
|
||||
.testcaselogtext {
|
||||
flex: 1 0;
|
||||
|
|
@ -412,7 +412,7 @@
|
|||
<table id="options">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<p class="important">Note: The options above only set the url parameters.
|
||||
<p class="important">Note: The options above only set the url parameters.
|
||||
You must reload the page for the options to take affect.</p>
|
||||
</details>
|
||||
<p>
|
||||
|
|
|
|||
6
tools/eslint-plugin-gpuweb-cts/index.js
Normal file
6
tools/eslint-plugin-gpuweb-cts/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'string-trailing-space': require('./trailing-space-anywhere'),
|
||||
'string-tabs': require('./tabs-anywhere'),
|
||||
},
|
||||
};
|
||||
8
tools/eslint-plugin-gpuweb-cts/package.json
Normal file
8
tools/eslint-plugin-gpuweb-cts/package.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "eslint-plugin-gpuweb-cts",
|
||||
"version": "0.0.0",
|
||||
"author": "WebGPU CTS Contributors",
|
||||
"private": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "index.js"
|
||||
}
|
||||
29
tools/eslint-plugin-gpuweb-cts/tabs-anywhere.js
Normal file
29
tools/eslint-plugin-gpuweb-cts/tabs-anywhere.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Indentation tabs are not allowed, even in multiline strings, due to WPT lint rules. This rule simply disallows tabs anywhere.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create: context => {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
Program: node => {
|
||||
for (let lineIdx = 0; lineIdx < sourceCode.lines.length; ++lineIdx) {
|
||||
const line = sourceCode.lines[lineIdx];
|
||||
const matches = line.matchAll(/\t/g);
|
||||
for (const match of matches) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { line: lineIdx + 1, column: match.index },
|
||||
message: 'Tabs not allowed.',
|
||||
// fixer is hard to implement, so not implemented.
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
29
tools/eslint-plugin-gpuweb-cts/trailing-space-anywhere.js
Normal file
29
tools/eslint-plugin-gpuweb-cts/trailing-space-anywhere.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Trailing spaces are not allowed, even in multiline strings, due to WPT lint rules.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create: context => {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
Program: node => {
|
||||
for (let lineIdx = 0; lineIdx < sourceCode.lines.length; ++lineIdx) {
|
||||
const line = sourceCode.lines[lineIdx];
|
||||
const match = /\s+$/.exec(line);
|
||||
if (match) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { line: lineIdx + 1, column: match.index },
|
||||
message: 'Trailing spaces not allowed.',
|
||||
// fixer is hard to implement, so not implemented.
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue