- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 32
Report better errors for values parsed from a PostCSS stylesheet #102
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
Changes from all commits
9e6cc98
              1698e19
              7de90f1
              2c86e26
              e0c32ae
              b8b374a
              dfc8324
              0f73974
              9e5e985
              a7ba2b6
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* | ||
| Copyright © 2018 Andrew Powell | ||
|  | ||
| This Source Code Form is subject to the terms of the Mozilla Public | ||
| License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|  | ||
| The above copyright notice and this permission notice shall be | ||
| included in all copies or substantial portions of this Source Code Form. | ||
| */ | ||
|  | ||
| // A PostCSS Input that exposes a substring of a larger Input as though it were | ||
| // the entire text to be parsed. | ||
| module.exports = class SubInput { | ||
| constructor(css, context, lineInContext, columnInContext) { | ||
| this.css = css; | ||
| this.context = context; | ||
| this.lineInContext = lineInContext; | ||
| this.columnInContext = columnInContext; | ||
| } | ||
|  | ||
| error(message, line, column, opts = {}) { | ||
| let lineInContext; | ||
| let columnInContext; | ||
| if (line === 1) { | ||
| lineInContext = this.lineInContext; // eslint-disable-line prefer-destructuring | ||
| columnInContext = column + this.columnInContext - 1; | ||
| } else { | ||
| lineInContext = this.lineInContext + line - 1; | ||
| columnInContext = column; | ||
| } | ||
|  | ||
| return this.context.error(message, lineInContext, columnInContext, opts); | ||
| } | ||
|  | ||
| origin(line, column) { | ||
| let lineInContext; | ||
| let columnInContext; | ||
| if (line === 1) { | ||
| lineInContext = this.lineInContext; // eslint-disable-line prefer-destructuring | ||
| columnInContext = column + this.columnInContext - 1; | ||
| } else { | ||
| lineInContext = this.lineInContext + line - 1; | ||
| } | ||
|  | ||
| return this.context.origin(lineInContext, columnInContext); | ||
| } | ||
|  | ||
| mapResolve(file) { | ||
| return this.context.mapResolve(file); | ||
| } | ||
|  | ||
| get from() { | ||
| return this.context.from; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -11,11 +11,58 @@ | |
| const Input = require('postcss/lib/input'); | ||
|  | ||
| const Parser = require('./ValuesParser'); | ||
| const SubInput = require('./SubInput'); | ||
| const { stringify } = require('./ValuesStringifier'); | ||
|  | ||
| const NEWLINE = '\n'.charCodeAt(0); | ||
| const FEED = '\f'.charCodeAt(0); | ||
| const CR = '\r'.charCodeAt(0); | ||
|  | ||
| function positionAfter(node, chunks) { | ||
| let { line } = node.source.start; | ||
| let { column } = node.source.start; | ||
| for (const chunk of chunks) { | ||
| for (let i = 0; i < chunk.length; i++) { | ||
| const code = chunk.charCodeAt(i); | ||
| if ( | ||
| code === NEWLINE || | ||
| code === FEED || | ||
| (code === CR && chunk.charCodeAt(i + 1) !== NEWLINE) | ||
| ) { | ||
| column = 1; | ||
| line += 1; | ||
| } else { | ||
| column += 1; | ||
| } | ||
| } | ||
| } | ||
|  | ||
| return { line, column }; | ||
| } | ||
|  | ||
| module.exports = { | ||
| parse(css, options) { | ||
| const input = new Input(css, options); | ||
| parse(css, opts) { | ||
| const options = opts || {}; | ||
|  | ||
| let input; | ||
| if (options.context) { | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 
 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added documentation, let me know what you think. | ||
| if (!options.lineInContext || !options.columnInContext) { | ||
| throw new RangeError( | ||
| 'If the context option is passed, lineInContext and ' + | ||
| 'columnInContext must also be passed.' | ||
| ); | ||
| } | ||
|  | ||
| input = new SubInput(css, options.context, options.lineInContext, options.columnInContext); | ||
| } else if (options.lineInContext || options.columnInContext) { | ||
| throw new RangeError( | ||
| "If the context option isn't passed, lineInContext and " + | ||
| 'columnInContext may not be passed.' | ||
| ); | ||
| } else { | ||
| input = new Input(css, options); | ||
| } | ||
|  | ||
| const parser = new Parser(input, options); | ||
|  | ||
| parser.parse(); | ||
|  | @@ -32,6 +79,26 @@ module.exports = { | |
| return parser.root; | ||
| }, | ||
|  | ||
| parseDeclValue(decl, options) { | ||
| const { line, column } = positionAfter(decl, [decl.prop, decl.raws.between]); | ||
| return module.exports.parse(decl.value, { | ||
| ...options, | ||
| context: decl.source.input, | ||
| lineInContext: line, | ||
| columnInContext: column | ||
| }); | ||
| }, | ||
|  | ||
| parseAtRuleParams(rule, options) { | ||
| const { line, column } = positionAfter(rule, ['@', rule.name, rule.raws.afterName]); | ||
| return module.exports.parse(rule.params, { | ||
| ...options, | ||
| context: rule.source.input, | ||
| lineInContext: line, | ||
| columnInContext: column | ||
| }); | ||
| }, | ||
|  | ||
| stringify, | ||
|  | ||
| nodeToString(node) { | ||
|  | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /* | ||
| Copyright © 2020 Andrew Powell | ||
|  | ||
| This Source Code Form is subject to the terms of the Mozilla Public | ||
| License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|  | ||
| The above copyright notice and this permission notice shall be | ||
| included in all copies or substantial portions of this Source Code Form. | ||
| */ | ||
| const test = require('ava'); | ||
|  | ||
| const postcss = require('postcss'); | ||
| const scss = require('postcss-scss'); | ||
|  | ||
| const { parse, parseDeclValue, parseAtRuleParams } = require('../lib'); | ||
|  | ||
| const { throws, functionCall } = require('./fixtures/errors'); | ||
|  | ||
| function snapshotError(t, callback) { | ||
| try { | ||
| callback(); | ||
| throw Error('Expected an error.'); | ||
| } catch (error) { | ||
| if (!('showSourceCode' in error)) { | ||
| throw error; | ||
| } | ||
|  | ||
| t.snapshot(error); | ||
| t.snapshot(error.showSourceCode(false)); | ||
| } | ||
| } | ||
|  | ||
| test(throws.decl, (t) => { | ||
| const root = postcss.parse(throws.decl, { | ||
| from: 'file:///fixtures/errors.js' | ||
| }); | ||
| snapshotError(t, () => parseDeclValue(root.nodes[0].nodes[0])); | ||
| }); | ||
|  | ||
| test(throws.atRule, (t) => { | ||
| const root = postcss.parse(throws.atRule, { | ||
| from: 'file:///fixtures/errors.js' | ||
| }); | ||
| snapshotError(t, () => parseAtRuleParams(root.nodes[0])); | ||
| }); | ||
|  | ||
| test(throws.interpolation, (t) => { | ||
| const root = scss.parse(throws.interpolation, { | ||
| from: 'file:///fixtures/errors.js' | ||
| }); | ||
|  | ||
| const [decl] = root.nodes[0].nodes; | ||
| snapshotError(t, () => | ||
| parse(decl.prop, { | ||
| interpolation: { prefix: '#' }, | ||
| context: root.source.input, | ||
| lineInContext: decl.source.start.line, | ||
| columnInContext: decl.source.start.column | ||
| }) | ||
| ); | ||
| }); | ||
|  | ||
| test(functionCall, (t) => { | ||
| const root = scss.parse(functionCall, { | ||
| from: 'file:///fixtures/errors.js' | ||
| }); | ||
|  | ||
| const value = parseDeclValue(root.nodes[0].nodes[0]); | ||
| value.walk((node) => { | ||
| delete node.parent; // eslint-disable-line no-param-reassign | ||
| }); | ||
|  | ||
| snapshotError(t, () => | ||
| value.walkFuncs((func) => { | ||
| if (func.name === 'var') throw func.error('Undefined variable!'); | ||
| }) | ||
| ); | ||
| }); | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| Copyright © 2020 Andrew Powell | ||
|  | ||
| This Source Code Form is subject to the terms of the Mozilla Public | ||
| License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|  | ||
| The above copyright notice and this permission notice shall be | ||
| included in all copies or substantial portions of this Source Code Form. | ||
| */ | ||
| module.exports = { | ||
| throws: { | ||
| decl: 'a {\n b: +-2.;\n}', | ||
| atRule: '@foo +-2. {\n a {\n b: c;\n }\n}', | ||
| interpolation: 'a {\n background-#{+-2.}: white;\n}' | ||
| }, | ||
| functionCall: 'p {\n color: rgb(var(--some-color) / 70%);\n}' | ||
| }; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without seeing how this is used (via tests) I can't comment on whether or not this is a good name for this.
Also, why does this not inherit from
Input?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not used directly, just instantiated from
lib/input.js.We're not re-using any of
Input's methods, we're just matching its API. In static-typing terms, we're implementingInput's interface rather than extending its implementation.