Skip to content

Commit

Permalink
postcss-tape : stacktrace improvements (#1233)
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmenke authored Dec 31, 2023
1 parent 945e817 commit e9dd9fc
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 16 deletions.
4 changes: 4 additions & 0 deletions packages/postcss-tape/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to PostCSS Tape

### Unreleased (patch)

- Improve stack traces for issues in `package.json` of postcss plugins.

### 4.1.1

_December 28, 2023_
Expand Down
2 changes: 1 addition & 1 deletion packages/postcss-tape/dist/index.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/* node:coverage disable */
"use strict";var e=require("node:assert/strict"),t=require("fs/promises"),s=require("fs"),n=require("path"),o=require("postcss"),i=require("postcss-8.4"),r=require("node:test");const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}noopPlugin.postcss=!0;const a={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},c={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},p={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};exports.atRuleClonerPlugin=p,exports.declarationClonerPlugin=a,exports.postcssTape=function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const n=await t.readFile("./package.json","utf-8"),o=JSON.parse(n);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(o.keywords),e.ok(o.keywords.includes("postcss-plugin"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=o.name;if(t.startsWith("@")){t=o.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),`package name "${t}" does not start with "postcss-"`)})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(o.peerDependencies),e.ok(Object.keys(Object(o.peerDependencies)).includes("postcss")),e.ok(!Object.keys(Object(o.dependencies)).includes("postcss"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=n.join(".","test",...p.split(":")[0].split(n.posix.sep)),d=n.join(".","test",...p.replace(/:/g,".").split(n.posix.sep)),g="css";let f=`${u}.${g}`,w=`${d}.expect.${g}`,m=`${d}.result.${g}`;l.source&&(f=n.join(".","test",l.source)),l.expect&&(w=n.join(".","test",l.expect)),l.result&&(m=n.join(".","test",l.result));const y=l.plugins??[a(l.options)],k=await fileContentsOrEmptyString(f),S=await fileContentsOrEmptyString(w);let x,E=!1;try{x=await o(y).process(k,{from:f,to:m,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),E=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!E,l.exception,"expected an exception but got none");const h=x.css.toString();{const e=[t.writeFile(m,h,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(w,h,"utf8")),await Promise.all(e)}S||e.ok(s.existsSync(w),`Missing expect file: "${w}"`),await r.test("has expected output",(()=>{e.strictEqual(h,S),e.deepStrictEqual(x.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!x.map.toJSON().sources.includes("<no source>"),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(m),s=await o([noopPlugin()]).process(t,{from:m,to:m,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:o([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(y).process(k,{from:f,to:m,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),h)}))}))}))}},exports.ruleClonerPlugin=c;
"use strict";var e=require("node:assert/strict"),s=require("node:fs/promises"),t=require("node:fs"),n=require("node:path"),o=require("postcss"),r=require("postcss-8.4"),i=require("node:test"),a=require("node:url");const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await s.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}noopPlugin.postcss=!0;const c={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},p={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(s){e.has(s)||"to-clone"===s.selector&&(e.add(s),s.cloneBefore({selector:"cloned"}))}}}},l={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(s){if(!e.has(s))return"to-clone"===s.params?(e.add(s),void s.cloneBefore({params:"cloned"})):"to-clone"===s.name?(e.add(s),void s.cloneBefore({name:"cloned"})):void 0}}}};class PackageDescriptionError extends Error{constructor(e,s){super(e),this.name="PackageDescriptionError",this.stack=`${this.name}: ${this.message}\n at "${s}" (${a.pathToFileURL(n.resolve("package.json")).pathname})`}}exports.atRuleClonerPlugin=l,exports.declarationClonerPlugin=c,exports.postcssTape=function postcssTape(a,c){c=c??{},i("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),i("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),i("`postcssPlugin` is set on a plugin instance",(()=>{const s=a();e.ok(s.postcssPlugin),e.strictEqual(typeof s.postcssPlugin,"string")})),i("package.json",(async t=>{const n=await s.readFile("./package.json","utf-8"),o=JSON.parse(n);await t.test('includes "postcss-plugin" keyword',(()=>{e.ok(o.keywords?.includes("postcss-plugin"),new PackageDescriptionError('Missing "postcss-plugin" keyword in package.json',"keywords"))})),await t.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let s=o.name;if(s.startsWith("@")){s=o.name.split("/").slice(1).join("/")}e.ok(s.startsWith("postcss-"),new PackageDescriptionError(`package name "${s}" does not start with "postcss-"`,"name"))})),await t.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(Object.keys(Object(o.peerDependencies)).includes("postcss"),new PackageDescriptionError('"postcss" is not listed in "peerDependencies"',"peerDependencies")),e.ok(!Object.keys(Object(o.dependencies)).includes("postcss"),new PackageDescriptionError('"postcss" must not be listed in "dependencies"',"dependencies"))}))}));const p=a().postcssPlugin;return async c=>{await i(p,(async i=>{for(const p in c)await i.test(p,(async i=>{const l=c[p];l.before&&await l.before();const u=n.join(".","test",...p.split(":")[0].split(n.posix.sep)),d=n.join(".","test",...p.replace(/:/g,".").split(n.posix.sep)),g="css";let w=`${u}.${g}`,f=`${d}.expect.${g}`,m=`${d}.result.${g}`;l.source&&(w=n.join(".","test",l.source)),l.expect&&(f=n.join(".","test",l.expect)),l.result&&(m=n.join(".","test",l.result));const k=l.plugins??[a(l.options)],y=await fileContentsOrEmptyString(w),E=await fileContentsOrEmptyString(f);let h,x=!1;try{h=await o(k).process(y,{from:w,to:m,map:{inline:!1,annotation:!1}})}catch(s){if(reduceInformationInCssSyntaxError(s),x=!0,l.exception&&l.exception.test(s.message))return;e.ifError(s)}e.notEqual(!x,l.exception,"expected an exception but got none");const S=h.css.toString();{const e=[s.writeFile(m,S,"utf8")];process.env.REWRITE_EXPECTS&&e.push(s.writeFile(f,S,"utf8")),await Promise.all(e)}E||e.ok(t.existsSync(f),`Missing expect file: "${f}"`),await i.test("has expected output",(()=>{e.strictEqual(S,E),e.deepStrictEqual(h.warnings().length,l.warnings??0,"Unexpected number warnings")})),await i.test("sourcemaps",(async()=>{e.ok(!h.map.toJSON().sources.includes("<no source>"),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await i.test("output is parsable with PostCSS",(async()=>{const s=await fileContentsOrEmptyString(m),t=await o([noopPlugin()]).process(s,{from:m,to:m,map:{inline:!1,annotation:!1}});e.deepStrictEqual(t.warnings(),[],"Unexpected warnings on second pass")})),await i.test("The oldest and current PostCSS version produce the same result",{skip:o([noopPlugin()]).version===r([noopPlugin()]).version},(async()=>{const s=await r(k).process(y,{from:w,to:m,map:{inline:!1,annotation:!1}});e.strictEqual(s.css.toString(),S)}))}))}))}},exports.ruleClonerPlugin=p;
/* node:coverage enable */
2 changes: 1 addition & 1 deletion packages/postcss-tape/dist/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/* node:coverage disable */
import e from"node:assert/strict";import t from"fs/promises";import s from"fs";import o from"path";import n from"postcss";import i from"postcss-8.4";import r from"node:test";const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const o=await t.readFile("./package.json","utf-8"),n=JSON.parse(o);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(n.keywords),e.ok(n.keywords.includes("postcss-plugin"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=n.name;if(t.startsWith("@")){t=n.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),`package name "${t}" does not start with "postcss-"`)})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(n.peerDependencies),e.ok(Object.keys(Object(n.peerDependencies)).includes("postcss")),e.ok(!Object.keys(Object(n.dependencies)).includes("postcss"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=o.join(".","test",...p.split(":")[0].split(o.posix.sep)),d=o.join(".","test",...p.replace(/:/g,".").split(o.posix.sep)),f="css";let m=`${u}.${f}`,g=`${d}.expect.${f}`,w=`${d}.result.${f}`;l.source&&(m=o.join(".","test",l.source)),l.expect&&(g=o.join(".","test",l.expect)),l.result&&(w=o.join(".","test",l.result));const y=l.plugins??[a(l.options)],k=await fileContentsOrEmptyString(m),S=await fileContentsOrEmptyString(g);let x,E=!1;try{x=await n(y).process(k,{from:m,to:w,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),E=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!E,l.exception,"expected an exception but got none");const h=x.css.toString();{const e=[t.writeFile(w,h,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(g,h,"utf8")),await Promise.all(e)}S||e.ok(s.existsSync(g),`Missing expect file: "${g}"`),await r.test("has expected output",(()=>{e.strictEqual(h,S),e.deepStrictEqual(x.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!x.map.toJSON().sources.includes("<no source>"),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(w),s=await n([noopPlugin()]).process(t,{from:w,to:w,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:n([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(y).process(k,{from:m,to:w,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),h)}))}))}))}}noopPlugin.postcss=!0;const a={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},c={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},p={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};export{p as atRuleClonerPlugin,a as declarationClonerPlugin,postcssTape,c as ruleClonerPlugin};
import e from"node:assert/strict";import t from"node:fs/promises";import s from"node:fs";import o from"node:path";import n from"postcss";import i from"postcss-8.4";import r from"node:test";import a from"node:url";const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});async function fileContentsOrEmptyString(e){try{return await t.readFile(e,"utf8")}catch(e){return""}}function reduceInformationInCssSyntaxError(e){process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}function postcssTape(a,c){c=c??{},r("`postcss` flag is set on exported plugin creator",(()=>{e.strictEqual(a.postcss,!0)})),r("exported plugin creator is a function",(()=>{e.strictEqual(typeof a,"function")})),r("`postcssPlugin` is set on a plugin instance",(()=>{const t=a();e.ok(t.postcssPlugin),e.strictEqual(typeof t.postcssPlugin,"string")})),r("package.json",(async s=>{const o=await t.readFile("./package.json","utf-8"),n=JSON.parse(o);await s.test('includes "postcss-plugin" keyword',(()=>{e.ok(n.keywords?.includes("postcss-plugin"),new PackageDescriptionError('Missing "postcss-plugin" keyword in package.json',"keywords"))})),await s.test('name starts with "postcss-"',{skip:c?.skipPackageNameCheck},(()=>{let t=n.name;if(t.startsWith("@")){t=n.name.split("/").slice(1).join("/")}e.ok(t.startsWith("postcss-"),new PackageDescriptionError(`package name "${t}" does not start with "postcss-"`,"name"))})),await s.test("`postcss` is a peer dependency and not a direct dependency",{skip:"postcssTapeSelfTest"in a},(()=>{e.ok(Object.keys(Object(n.peerDependencies)).includes("postcss"),new PackageDescriptionError('"postcss" is not listed in "peerDependencies"',"peerDependencies")),e.ok(!Object.keys(Object(n.dependencies)).includes("postcss"),new PackageDescriptionError('"postcss" must not be listed in "dependencies"',"dependencies"))}))}));const p=a().postcssPlugin;return async c=>{await r(p,(async r=>{for(const p in c)await r.test(p,(async r=>{const l=c[p];l.before&&await l.before();const u=o.join(".","test",...p.split(":")[0].split(o.posix.sep)),d=o.join(".","test",...p.replace(/:/g,".").split(o.posix.sep)),m="css";let g=`${u}.${m}`,f=`${d}.expect.${m}`,w=`${d}.result.${m}`;l.source&&(g=o.join(".","test",l.source)),l.expect&&(f=o.join(".","test",l.expect)),l.result&&(w=o.join(".","test",l.result));const k=l.plugins??[a(l.options)],y=await fileContentsOrEmptyString(g),E=await fileContentsOrEmptyString(f);let h,S=!1;try{h=await n(k).process(y,{from:g,to:w,map:{inline:!1,annotation:!1}})}catch(t){if(reduceInformationInCssSyntaxError(t),S=!0,l.exception&&l.exception.test(t.message))return;e.ifError(t)}e.notEqual(!S,l.exception,"expected an exception but got none");const x=h.css.toString();{const e=[t.writeFile(w,x,"utf8")];process.env.REWRITE_EXPECTS&&e.push(t.writeFile(f,x,"utf8")),await Promise.all(e)}E||e.ok(s.existsSync(f),`Missing expect file: "${f}"`),await r.test("has expected output",(()=>{e.strictEqual(x,E),e.deepStrictEqual(h.warnings().length,l.warnings??0,"Unexpected number warnings")})),await r.test("sourcemaps",(async()=>{e.ok(!h.map.toJSON().sources.includes("<no source>"),'Sourcemap is broken. This is most likely a newly created PostCSS AST Node without a value for "source". See: https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes')})),l.after&&await l.after(),await r.test("output is parsable with PostCSS",(async()=>{const t=await fileContentsOrEmptyString(w),s=await n([noopPlugin()]).process(t,{from:w,to:w,map:{inline:!1,annotation:!1}});e.deepStrictEqual(s.warnings(),[],"Unexpected warnings on second pass")})),await r.test("The oldest and current PostCSS version produce the same result",{skip:n([noopPlugin()]).version===i([noopPlugin()]).version},(async()=>{const t=await i(k).process(y,{from:g,to:w,map:{inline:!1,annotation:!1}});e.strictEqual(t.css.toString(),x)}))}))}))}}noopPlugin.postcss=!0;const c={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},p={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{RuleExit(t){e.has(t)||"to-clone"===t.selector&&(e.add(t),t.cloneBefore({selector:"cloned"}))}}}},l={postcssPlugin:"at-rule-cloner",prepare(){const e=new WeakSet;return{AtRuleExit(t){if(!e.has(t))return"to-clone"===t.params?(e.add(t),void t.cloneBefore({params:"cloned"})):"to-clone"===t.name?(e.add(t),void t.cloneBefore({name:"cloned"})):void 0}}}};class PackageDescriptionError extends Error{constructor(e,t){super(e),this.name="PackageDescriptionError",this.stack=`${this.name}: ${this.message}\n at "${t}" (${a.pathToFileURL(o.resolve("package.json")).pathname})`}}export{l as atRuleClonerPlugin,c as declarationClonerPlugin,postcssTape,p as ruleClonerPlugin};
/* node:coverage enable */
2 changes: 1 addition & 1 deletion packages/postcss-tape/src/file-contents-or-empty-string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fs from 'fs/promises';
import fs from 'node:fs/promises';

export async function fileContentsOrEmptyString(path: string): Promise<string> {
try {
Expand Down
Loading

0 comments on commit e9dd9fc

Please sign in to comment.