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:
Kai Ninomiya 2023-05-24 21:08:56 -07:00 committed by GitHub
parent 1dd8ad4f79
commit 0be3bde33c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 152 additions and 57 deletions

View file

@ -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
View file

@ -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",

View file

@ -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",

View file

@ -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;
}`,

View file

@ -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.

View file

@ -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> {

View file

@ -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.
`

View file

@ -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.
`

View file

@ -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.
`

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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.
`
)

View file

@ -21,6 +21,6 @@
draw(cvs0.getContext('2d'));
draw(cvs1.getContext('2d'));
draw(cvs2.getContext('2d'));
</script>
</html>

View file

@ -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>

View file

@ -0,0 +1,6 @@
module.exports = {
rules: {
'string-trailing-space': require('./trailing-space-anywhere'),
'string-tabs': require('./tabs-anywhere'),
},
};

View 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"
}

View 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.
});
}
}
},
};
},
};

View 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.
});
}
}
},
};
},
};