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

Feature: check empty string reference for other resource in HTML(and SVG) #7318

Merged
94 changes: 94 additions & 0 deletions packages/core/integration-tests/test/html.js
Expand Up @@ -2704,4 +2704,98 @@ describe('html', function () {
assert(output.includes('<filter'));
assert(output.includes('<feGaussianBlur in="SourceGraphic" stdDeviation='));
});

it('should throw error with empty string reference to other resource', async function () {
await assert.rejects(
() =>
bundle(
path.join(__dirname, 'integration/html-empty-reference/index.html'),
{
mode: 'production',
},
),
{
name: 'BuildError',
diagnostics: [
{
message: 'src should not be empty string',
origin: '@parcel/transformer-html',
codeFrames: [
{
filePath: path.join(
__dirname,
'integration/html-empty-reference/index.html',
),
language: 'html',
codeHighlights: [
{
start: {
column: 1,
line: 1,
},
end: {
column: 14,
line: 1,
},
},
],
},
],
},

{
message: 'src should not be empty string',
origin: '@parcel/transformer-html',
codeFrames: [
{
filePath: path.join(
__dirname,
'integration/html-empty-reference/index.html',
),
language: 'html',
codeHighlights: [
{
start: {
column: 1,
line: 2,
},
end: {
column: 24,
line: 2,
},
},
],
},
],
},

{
message: 'href should not be empty string',
origin: '@parcel/transformer-html',
codeFrames: [
{
filePath: path.join(
__dirname,
'integration/html-empty-reference/index.html',
),
language: 'html',
codeHighlights: [
{
start: {
column: 1,
line: 3,
},
end: {
column: 16,
line: 3,
},
},
],
},
],
},
],
},
);
});
});
@@ -0,0 +1,3 @@
<img src="" />
<script src=""></script>
<link href="" />
1 change: 1 addition & 0 deletions packages/transformers/html/package.json
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@parcel/hash": "^2.0.1",
"@parcel/plugin": "^2.0.1",
"@parcel/diagnostic": "^2.0.1",
"nullthrows": "^1.1.1",
"posthtml": "^0.16.5",
"posthtml-parser": "^0.10.1",
Expand Down
21 changes: 20 additions & 1 deletion packages/transformers/html/src/HTMLTransformer.js
Expand Up @@ -10,6 +10,7 @@ import {render} from 'posthtml-render';
import semver from 'semver';
import collectDependencies from './dependencies';
import extractInlineAssets from './inline';
import ThrowableDiagnostic from '@parcel/diagnostic';

export default (new Transformer({
canReuseAST({ast}) {
Expand All @@ -33,9 +34,27 @@ export default (new Transformer({
if (asset.type === 'htm') {
asset.type = 'html';
}

asset.bundleBehavior = 'isolated';
let ast = nullthrows(await asset.getAST());
let hasScripts = collectDependencies(asset, ast);
let hasScripts;
try {
hasScripts = collectDependencies(asset, ast);
} catch (errors) {
throw new ThrowableDiagnostic({
diagnostic: errors.map(error => ({
message: error.message,
origin: '@parcel/transformer-html',
codeFrames: [
{
filePath: error.filePath,
language: 'html',
codeHighlights: [error.loc],
},
],
})),
});
}

const {assets: inlineAssets, hasScripts: hasInlineScripts} =
extractInlineAssets(asset, ast);
Expand Down
17 changes: 15 additions & 2 deletions packages/transformers/html/src/dependencies.js
Expand Up @@ -3,7 +3,6 @@
import type {AST, MutableAsset} from '@parcel/types';
import type {PostHTMLNode} from 'posthtml';
import PostHTML from 'posthtml';

// A list of all attributes that may produce a dependency
// Based on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
const ATTRS = {
Expand Down Expand Up @@ -115,6 +114,7 @@ export default function collectDependencies(
let isDirty = false;
let hasScripts = false;
let seen = new Set();
const errors = [];
PostHTML().walk.call(ast.program, node => {
let {tag, attrs} = node;
if (!attrs || seen.has(node)) {
Expand Down Expand Up @@ -156,7 +156,7 @@ export default function collectDependencies(
let href = attrs.href;
if (attrs.rel === 'manifest') {
// A hack to allow manifest.json rather than manifest.webmanifest.
// If a custom pipeline is used, it is responsible for running @parcel/transformer-webmanifest.
// If a custom pipestart is used, it is responsible for running @parcel/transformer-webmanifest.
Shinyaigeek marked this conversation as resolved.
Show resolved Hide resolved
if (!href.includes(':')) {
href = 'webmanifest:' + href;
}
Expand Down Expand Up @@ -254,6 +254,15 @@ export default function collectDependencies(
continue;
}

// Check for empty string
if (attrs[attr].length === 0) {
errors.push({
message: `${attr} should not be empty string`,
filePath: asset.filePath,
loc: node.location,
});
}

let elements = ATTRS[attr];
if (elements && elements.includes(node.tag)) {
let depHandler = getAttrDepHandler(attr);
Expand All @@ -274,5 +283,9 @@ export default function collectDependencies(
return node;
});

if (errors.length > 0) {
throw errors;
}

return hasScripts;
}
1 change: 1 addition & 0 deletions packages/transformers/svg/package.json
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@parcel/hash": "^2.0.1",
"@parcel/plugin": "^2.0.1",
"@parcel/diagnostic": "^2.0.1",
"nullthrows": "^1.1.1",
"posthtml": "^0.16.5",
"posthtml-parser": "^0.10.1",
Expand Down
19 changes: 18 additions & 1 deletion packages/transformers/svg/src/SVGTransformer.js
Expand Up @@ -7,6 +7,7 @@ import {parser as parse} from 'posthtml-parser';
import {render} from 'posthtml-render';
import collectDependencies from './dependencies';
import extractInlineAssets from './inline';
import ThrowableDiagnostic from '@parcel/diagnostic';

export default (new Transformer({
canReuseAST({ast}) {
Expand Down Expand Up @@ -36,7 +37,23 @@ export default (new Transformer({

const ast = nullthrows(await asset.getAST());

collectDependencies(asset, ast);
try {
collectDependencies(asset, ast);
} catch (errors) {
throw new ThrowableDiagnostic({
diagnostic: errors.map(error => ({
message: error.message,
origin: '@parcel/transformer-svg',
codeFrames: [
{
filePath: error.filePath,
language: 'svg',
codeHighlights: [error.loc],
},
],
})),
});
}

const inlineAssets = extractInlineAssets(asset, ast);

Expand Down
14 changes: 14 additions & 0 deletions packages/transformers/svg/src/dependencies.js
Expand Up @@ -77,6 +77,7 @@ const OPTIONS = {

export default function collectDependencies(asset: MutableAsset, ast: AST) {
let isDirty = false;
const errors = [];
PostHTML().walk.call(ast.program, node => {
// Ideally we'd have location information for specific attributes...
let getLoc = () =>
Expand Down Expand Up @@ -106,6 +107,15 @@ export default function collectDependencies(asset: MutableAsset, ast: AST) {
continue;
}

// Check for empty string
if (attrs[attr].length === 0) {
errors.push({
message: `${attr} should not be empty string`,
filePath: asset.filePath,
loc: node.location,
});
}

const elements = ATTRS[attr];
if (elements && elements.includes(node.tag)) {
let options = OPTIONS[tag]?.[attr];
Expand Down Expand Up @@ -143,6 +153,10 @@ export default function collectDependencies(asset: MutableAsset, ast: AST) {
return node;
});

if (errors.length > 0) {
throw errors;
}

if (isDirty) {
asset.setAST(ast);
}
Expand Down