diff --git a/.eslintrc.json b/.eslintrc.json index d183f888b..f95dfbb33 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,6 @@ { "env": { + "es6": true, "node": true }, "globals": {}, diff --git a/.travis.yml b/.travis.yml index 1bfa408e2..cefd0932b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,82 +2,39 @@ language: node_js compiler: gcc -env: - global: - - SKIP_SASS_BINARY_DOWNLOAD_FOR_CI=true - jobs: include: + - stage: lint + node_js: "12" + os: linux + install: npm install --ignore-scripts + script: npm run lint || exit 1; + - stage: test - node_js: "node" + node_js: "13" os: linux - before_script: npm run lint || exit 1; - after_success: npm run-script coverage; - - stage: platform-test - node_js: "node" + - stage: test + node_js: "13" os: osx - - stage: platform-test + - stage: test node_js: "12" os: linux - - stage: platform-test + after_success: npm run-script coverage; + - stage: test node_js: "12" os: osx - - stage: platform-test - node_js: "11" - os: linux - - stage: platform-test - node_js: "11" - os: osx - - stage: platform-test + - stage: test node_js: "10" os: linux - - stage: platform-test + - stage: test node_js: "10" os: osx - - stage: platform-test - node_js: "9" - os: linux - - stage: platform-test - node_js: "9" - os: osx - - stage: platform-test - node_js: "8" - os: linux - - stage: platform-test - node_js: "8" - os: osx - - stage: platform-test - node_js: "7" - os: linux - - stage: platform-test - node_js: "7" - os: osx - - stage: platform-test - node_js: "6" - os: linux - - stage: platform-test - node_js: "6" - os: osx - - stage: platform-test - node_js: "4" - os: linux - - stage: platform-test - node_js: "4" - os: osx - - stage: platform-test - node_js: "0.12" - os: linux - - stage: platform-test - node_js: "0.10" - os: linux addons: apt: sources: - ubuntu-toolchain-r-test packages: - - gcc-4.7 - - g++-4.7 - gcc-4.9 - g++-4.9 - gcc-6 @@ -92,16 +49,11 @@ before_install: export CXX="g++-6"; export LINK="gcc-6"; export LINKXX="g++-6"; - elif [[ $(node -v) =~ v1[01] ]]; then + else export CC="gcc-4.9"; export CXX="g++-4.9"; export LINK="gcc-4.9"; export LINKXX="g++-4.9"; - else - export CC="gcc-4.7"; - export CXX="g++-4.7"; - export LINK="gcc-4.7"; - export LINKXX="g++-4.7"; fi fi - nvm --version @@ -114,6 +66,7 @@ install: - npm install script: + - npm run build --force - npm test cache: diff --git a/appveyor.yml b/appveyor.yml index 077c65a3d..f774cf3fd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,47 +32,10 @@ - '%AppData%\npm-cache' environment: - SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - - nodejs_version: 0.10 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 0.12 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 1 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 2 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 3 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 4 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 5 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 6 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 7 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 8 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 9 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 10 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - nodejs_version: 11 - GYP_MSVS_VERSION: 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 12 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 @@ -86,6 +49,7 @@ - node --version - npm --version - npm install + - npm run build --force test: off @@ -144,35 +108,10 @@ - '%AppData%\npm-cache' environment: - SKIP_SASS_BINARY_DOWNLOAD_FOR_CI: true matrix: - - nodejs_version: 0.10 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 0.12 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 4 - GYP_MSVS_VERSION: 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - - nodejs_version: 6 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 7 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 8 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - - nodejs_version: 9 - GYP_MSVS_VERSION: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - nodejs_version: 10 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - - nodejs_version: 11 - GYP_MSVS_VERSION: 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - nodejs_version: 12 GYP_MSVS_VERSION: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 @@ -186,6 +125,7 @@ - node --version - npm --version - npm install + - npm run build --force test_script: - ps: set-location -path c:\projects\node_modules\node-sass diff --git a/bin/node-sass b/bin/node-sass index 54660ed6e..658f00b20 100755 --- a/bin/node-sass +++ b/bin/node-sass @@ -1,8 +1,8 @@ #!/usr/bin/env node var Emitter = require('events').EventEmitter, - forEach = require('async-foreach').forEach, - Gaze = require('gaze'), + promisify = require('util').promisify, + chokidar = require('chokidar'), meow = require('meow'), util = require('util'), path = require('path'), @@ -12,6 +12,7 @@ var Emitter = require('events').EventEmitter, watcher = require('../lib/watcher'), stdout = require('stdout-stream'), stdin = require('get-stdin'), + chalk = require('chalk'), fs = require('fs'); /** @@ -19,7 +20,7 @@ var Emitter = require('events').EventEmitter, */ var cli = meow({ - pkg: '../package.json', + pkg: require('../package.json'), version: sass.info, help: [ 'Usage:', @@ -58,51 +59,84 @@ var cli = meow({ ' --importer Path to .js file containing custom importer', ' --functions Path to .js file containing custom functions', ' --help Print usage info' - ].join('\n') -}, { - boolean: [ - 'error-bell', - 'follow', - 'indented-syntax', - 'omit-source-map-url', - 'quiet', - 'recursive', - 'source-map-embed', - 'source-map-contents', - 'source-comments', - 'watch' - ], - string: [ - 'functions', - 'importer', - 'include-path', - 'indent-type', - 'linefeed', - 'output', - 'output-style', - 'precision', - 'source-map-root' - ], - alias: { - c: 'source-comments', - i: 'indented-syntax', - q: 'quiet', - o: 'output', - r: 'recursive', - x: 'omit-source-map-url', - v: 'version', - w: 'watch' + ].join('\n'), + flags: { + 'error-bell': { + type: 'boolean', + }, + 'follow': { + type: 'boolean', + }, + 'indented-syntax': { + type: 'boolean', + alias: 'i', + }, + 'omit-source-map-url': { + type: 'boolean', + alias: 'x', + }, + 'quiet': { + type: 'boolean', + alias: 'q', + default: false, + }, + 'recursive': { + type: 'boolean', + alias: 'r', + default: true, + }, + 'source-map-embed': { + type: 'boolean', + }, + 'source-map-contents': { + type: 'boolean', + }, + 'source-comments': { + type: 'boolean', + alias: 'c', + }, + 'watch': { + type: 'boolean', + alias: 'w', + }, + 'functions': { + type: 'string', + }, + 'importer': { + type: 'string', + }, + 'include-path': { + type: 'string', + default: process.cwd(), + }, + 'indent-type': { + type: 'string', + default: 'space', + }, + 'indent-width': { + type: 'string', + default: 2, + }, + 'linefeed': { + type: 'string', + default: 'lf', + }, + 'output': { + type: 'string', + alias: 'o', + }, + 'output-style': { + type: 'string', + default: 'nested', + }, + 'precision': { + type: 'string', + default: 5, + }, + 'source-map-root': { + type: 'string', + }, }, - default: { - 'include-path': process.cwd(), - 'indent-type': 'space', - 'indent-width': 2, - linefeed: 'lf', - 'output-style': 'nested', - precision: 5, - quiet: false, - recursive: true - } }); /** @@ -236,40 +270,40 @@ function getOptions(args, options) { function watch(options, emitter) { var handler = function(files) { - files.added.forEach(function(file) { - var watch = gaze.watched(); - Object.keys(watch).forEach(function (dir) { - if (watch[dir].indexOf(file) !== -1) { - gaze.add(file); - } - }); - }); - - files.changed.forEach(function(file) { + files.forEach(function(file) { if (path.basename(file)[0] !== '_') { - renderFile(file, options, emitter); + renderFile(file, options, emitter, function(err) { + if (err) { + emitter.emit('error', chalk.red(err)); + } + }); } }); - - files.removed.forEach(function(file) { - gaze.remove(file); - }); }; - var gaze = new Gaze(); - gaze.add(watcher.reset(options)); - gaze.on('error', emitter.emit.bind(emitter, 'error')); + watcher.reset(options); - gaze.on('changed', function(file) { - handler(watcher.changed(file)); + var service = chokidar.watch(options.includePath, { + ignore: /(?!\.s[ac]ss$)/, + followSymlinks: options.follow, }); - gaze.on('added', function(file) { - handler(watcher.added(file)); - }); + service.on('ready', function() { + service.on('error', function(error) { + emitter.emit.error(error); + }); + + service.on('change', function(file) { + handler(watcher.changed(file)); + }); - gaze.on('deleted', function(file) { - handler(watcher.removed(file)); + service.on('add', function(file) { + handler(watcher.added(file)); + }); + + service.on('unlink', function(file) { + handler(watcher.removed(file)); + }); }); } @@ -300,7 +334,7 @@ function run(options, emitter) { } if (options.importer) { - if ((path.resolve(options.importer) === path.normalize(options.importer).replace(/(.+)([\/|\\])$/, '$1'))) { + if ((path.resolve(options.importer) === path.normalize(options.importer).replace(/(.+)([/|\\])$/, '$1'))) { options.importer = require(options.importer); } else { options.importer = require(path.resolve(options.importer)); @@ -308,19 +342,39 @@ function run(options, emitter) { } if (options.functions) { - if ((path.resolve(options.functions) === path.normalize(options.functions).replace(/(.+)([\/|\\])$/, '$1'))) { + if ((path.resolve(options.functions) === path.normalize(options.functions).replace(/(.+)([/|\\])$/, '$1'))) { options.functions = require(options.functions); } else { options.functions = require(path.resolve(options.functions)); } } - if (options.watch) { - watch(options, emitter); - } else if (options.directory) { - renderDir(options, emitter); + if (options.directory) { + renderDir(options, emitter, function(err) { + if (err) { + emitter.emit('error', chalk.red(err)); + return process.exit(1); + } + + if (options.watch) { + watch(options, emitter); + } else { + process.exit(); + } + }); } else { - render(options, emitter); + render(options, emitter, function(err) { + if (err) { + emitter.emit('error', chalk.red(err)); + return process.exit(1); + } + + if (options.watch) { + watch(options, emitter); + } else { + process.exit(); + } + }); } } @@ -332,12 +386,12 @@ function run(options, emitter) { * @param {Object} emitter * @api private */ -function renderFile(file, options, emitter) { +function renderFile(file, options, emitter, done) { options = getOptions([path.resolve(file)], options); if (options.watch && !options.quiet) { emitter.emit('info', util.format('=> changed: %s', file)); } - render(options, emitter); + render(options, emitter, done); } /** @@ -347,7 +401,7 @@ function renderFile(file, options, emitter) { * @param {Object} emitter * @api private */ -function renderDir(options, emitter) { +function renderDir(options, emitter, done) { var globPath = path.resolve(options.directory, globPattern(options)); glob(globPath, { ignore: '**/_*', follow: options.follow }, function(err, files) { if (err) { @@ -356,16 +410,19 @@ function renderDir(options, emitter) { return emitter.emit('error', 'No input file was found.'); } - forEach(files, function(subject) { - emitter.once('done', this.async()); - renderFile(subject, options, emitter); - }, function(successful, arr) { - var outputDir = path.join(process.cwd(), options.output); - if (!options.quiet) { - emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir)); - } - process.exit(); - }); + Promise.all(files.map(function(subject) { + return promisify(renderFile)(subject, options, emitter); + })) + .then(function(res) { + var outputDir = path.join(process.cwd(), options.output); + if (!options.quiet) { + emitter.emit('info', util.format('Wrote %s CSS files to %s', res.length, outputDir)); + } + }) + .then(done) + .catch(function() { + done(new Error('Some files failed to compile')); + }); }); } @@ -404,7 +461,7 @@ if (options.src) { } run(options, emitter); } else if (!process.stdin.isTTY) { - stdin(function(data) { + stdin().then(function(data) { options.data = data; options.stdin = true; run(options, emitter); diff --git a/binding.gyp b/binding.gyp index 8b3415293..61394aa92 100644 --- a/binding.gyp +++ b/binding.gyp @@ -27,6 +27,8 @@ 'SetChecksum': 'true' } }, + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], 'xcode_settings': { 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', 'CLANG_CXX_LIBRARY': 'libc++', @@ -34,8 +36,11 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', 'MACOSX_DEPLOYMENT_TARGET': '10.7' }, + "dependencies": [ + '=0.10.0" + "node": ">= 10" }, "main": "lib/index.js", "nodeSassConfig": { @@ -53,31 +53,32 @@ "style" ], "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^1.1.1", - "cross-spawn": "^3.0.0", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", + "chalk": "^2.4.2", + "chokidar": "^3.3.0", + "cross-spawn": "^7.0.1", + "get-stdin": "^7.0.0", + "glob": "^7.1.6", "in-publish": "^2.0.0", "lodash": "^4.17.15", - "meow": "^3.7.0", + "meow": "^5.0.0", "mkdirp": "^0.5.1", - "nan": "^2.13.2", - "node-gyp": "^3.8.0", + "nan": "^2.14.0", + "node-addon-api": "^1.7.1", "npmlog": "^4.0.0", "request": "^2.88.0", - "sass-graph": "^2.2.4", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" + "sass-graph": "^3.0.4", + "stdout-stream": "^1.4.1", + "true-case-path": "^2.2.1", + "unicode-9.0.0": "^0.7.5" }, "devDependencies": { - "coveralls": "^3.0.2", - "eslint": "^3.4.0", - "fs-extra": "^0.30.0", + "coveralls": "^3.0.7", + "eslint": "^6.6.0", + "fs-extra": "^8.1.0", "istanbul": "^0.4.2", - "mocha": "^3.1.2", + "mocha": "^6.2.2", "mocha-lcov-reporter": "^1.2.0", + "node-gyp": "^6.0.1", "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", diff --git a/scripts/build.js b/scripts/build.js index 7bbba5ee4..5dcdd4d70 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -19,9 +19,9 @@ function afterBuild(options) { var install = sass.getBinaryPath(); var target = path.join(__dirname, '..', 'build', options.debug ? 'Debug' : - process.config.target_defaults - ? process.config.target_defaults.default_configuration - : 'Release', + process.config.target_defaults + ? process.config.target_defaults.default_configuration + : 'Release', 'binding.node'); mkdir(path.dirname(install), function(err) { diff --git a/scripts/coverage.js b/scripts/coverage.js index 33836e9cf..1f20476b2 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -29,7 +29,7 @@ function coverage() { fs.readFile(path.join('coverage', 'lcov.info'), function(err, data) { if (err) { console.error(err); } coveralls.handleInput(data.toString(), - function (err) { if (err) { console.error(err); } }); + function (err) { if (err) { console.error(err); } }); }); }); lcov.writeReport(collector, true); @@ -46,30 +46,32 @@ function coverage() { if (err) { throw err; } mkdirp('lib-cov', function(err) { if (err) { throw err; } - fs.writeFile(path.join('lib-cov', source), - instrumenter.instrumentSync(data.toString(), - path.join('lib', source)), - function(err) { - if (err) { throw err; } - instrumentedfiles.push(source); - if (instrumentedfiles.length === sourcefiles.length) { - fs.readdirSync('test').filter(function(file){ - return file.substr(-6) === 'api.js' || - file.substr(-11) === 'runtime.js' || - file.substr(-7) === 'spec.js'; - }).forEach(function(file){ - mocha.addFile( - path.join('test', file) - ); - }); - process.env.NODESASS_COV = 1; - mocha.reporter(rep).run(function(failures) { - process.on('exit', function () { - process.exit(failures); - }); - }); - } - }); + fs.writeFile( + path.join('lib-cov', source), + instrumenter.instrumentSync(data.toString(), path.join('lib', source)), + function(err) { + if (err) { throw err; } + instrumentedfiles.push(source); + if (instrumentedfiles.length === sourcefiles.length) { + fs.readdirSync('test').filter(function(file){ + return false || + file.substr(-6) === 'api.js' || + file.substr(-11) === 'runtime.js' || + file.substr(-7) === 'spec.js'; + }).forEach(function(file){ + mocha.addFile( + path.join('test', file) + ); + }); + process.env.NODESASS_COV = 1; + mocha.reporter(rep).run(function(failures) { + process.on('exit', function () { + process.exit(failures); + }); + }); + } + } + ); }); }); }; diff --git a/scripts/install.js b/scripts/install.js index 6febbe498..30cc6420d 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -68,22 +68,22 @@ function download(url, dest, cb) { } } }) - .on('response', function(response) { - var length = parseInt(response.headers['content-length'], 10); - var progress = log.newItem('', length); - - // The `progress` is true by default. However if it has not - // been explicitly set it's `undefined` which is considered - // as far as npm is concerned. - if (process.env.npm_config_progress === 'true') { - log.enableProgress(); - - response.on('data', function(chunk) { - progress.completeWork(chunk.length); - }) - .on('end', progress.finish); - } - }); + .on('response', function(response) { + var length = parseInt(response.headers['content-length'], 10); + var progress = log.newItem('', length); + + // The `progress` is true by default. However if it has not + // been explicitly set it's `undefined` which is considered + // as far as npm is concerned. + if (process.env.npm_config_progress === 'true') { + log.enableProgress(); + + response.on('data', function(chunk) { + progress.completeWork(chunk.length); + }) + .on('end', progress.finish); + } + }); } catch (err) { cb(err); } @@ -96,11 +96,6 @@ function download(url, dest, cb) { */ function checkAndDownloadBinary() { - if (process.env.SKIP_SASS_BINARY_DOWNLOAD_FOR_CI) { - console.log('Skipping downloading binaries on CI builds'); - return; - } - var cachedBinary = sass.getCachedBinary(), cachePath = sass.getBinaryCachePath(), binaryPath = sass.getBinaryPath(); diff --git a/src/binding.cpp b/src/binding.cpp index c8376e982..7078fa4af 100644 --- a/src/binding.cpp +++ b/src/binding.cpp @@ -1,9 +1,9 @@ -#include #include #include "sass_context_wrapper.h" #include "custom_function_bridge.h" #include "create_string.h" #include "sass_types/factory.h" +#include Sass_Import_List sass_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { @@ -25,28 +25,30 @@ union Sass_Value* sass_custom_function(const union Sass_Value* s_args, Sass_Func CustomFunctionBridge& bridge = *(static_cast(cookie)); std::vector argv; - for (unsigned l = sass_list_get_length(s_args), i = 0; i < l; i++) { + for (size_t l = sass_list_get_length(s_args), i = 0; i < l; i++) { argv.push_back((void*)sass_list_get_value(s_args, i)); } return bridge(argv); } -int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapper* ctx_w, bool is_file, bool is_sync) { - Nan::HandleScope scope; +int ExtractOptions(napi_env e, napi_value options, void* cptr, sass_context_wrapper* ctx_w, bool is_file, bool is_sync) { + // TODO: Enable napi_close_handle_scope during a pending exception state. + //Napi::HandleScope scope(e); struct Sass_Context* ctx; - v8::Local result_ = Nan::Get( - options, - Nan::New("result").ToLocalChecked() - ).ToLocalChecked(); - if (!result_->IsObject()) { - Nan::ThrowTypeError("\"result\" element is not an object"); + napi_value result_; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "result", &result_)); + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(e, result_, &t)); + + if (t != napi_object) { + CHECK_NAPI_RESULT(napi_throw_type_error(e, nullptr, "\"result\" element is not an object")); return -1; } - ctx_w->result.Reset(result_.As()); + CHECK_NAPI_RESULT(napi_create_reference(e, result_, 1, &ctx_w->result)); if (is_file) { ctx_w->fctx = (struct Sass_File_Context*) cptr; @@ -65,61 +67,123 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp ctx_w->request.data = ctx_w; // async (callback) style - v8::Local success_callback = v8::Local::Cast(Nan::Get(options, Nan::New("success").ToLocalChecked()).ToLocalChecked()); - v8::Local error_callback = v8::Local::Cast(Nan::Get(options, Nan::New("error").ToLocalChecked()).ToLocalChecked()); + napi_value success_cb; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "success", &success_cb)); + napi_valuetype cb_type; + CHECK_NAPI_RESULT(napi_typeof(e, success_cb, &cb_type)); + if (cb_type == napi_function) { + CHECK_NAPI_RESULT(napi_create_reference(e, success_cb, 1, &ctx_w->success_callback)); + } - ctx_w->success_callback = new Nan::Callback(success_callback); - ctx_w->error_callback = new Nan::Callback(error_callback); + napi_value error_cb; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "error", &error_cb)); + CHECK_NAPI_RESULT(napi_typeof(e, error_cb, &cb_type)); + if (cb_type == napi_function) { + CHECK_NAPI_RESULT(napi_create_reference(e, error_cb, 1, &ctx_w->error_callback)); + } } if (!is_file) { - ctx_w->file = create_string(Nan::Get(options, Nan::New("file").ToLocalChecked())); + napi_value propertyFile; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "file", &propertyFile)); + + ctx_w->file = create_string(e, propertyFile); sass_option_set_input_path(sass_options, ctx_w->file); } - int indent_len = Nan::To( - Nan::Get( - options, - Nan::New("indentWidth").ToLocalChecked() - ).ToLocalChecked()).FromJust(); + napi_value propertyIndentWidth; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "indentWidth", &propertyIndentWidth)); + CHECK_NAPI_RESULT(napi_coerce_to_number(e, propertyIndentWidth, &propertyIndentWidth)); + int32_t indent_width_len; + CHECK_NAPI_RESULT(napi_get_value_int32(e, propertyIndentWidth, &indent_width_len)); + + ctx_w->indent = (char*)malloc(indent_width_len + 1); - ctx_w->indent = (char*)malloc(indent_len + 1); + napi_value propertyIndentType; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "indentType", &propertyIndentType)); + CHECK_NAPI_RESULT(napi_coerce_to_number(e, propertyIndentType, &propertyIndentType)); + int32_t indent_type_len; + CHECK_NAPI_RESULT(napi_get_value_int32(e, propertyIndentType, &indent_type_len)); strcpy(ctx_w->indent, std::string( - indent_len, - Nan::To( - Nan::Get( - options, - Nan::New("indentType").ToLocalChecked() - ).ToLocalChecked()).FromJust() == 1 ? '\t' : ' ' - ).c_str()); - - ctx_w->linefeed = create_string(Nan::Get(options, Nan::New("linefeed").ToLocalChecked())); - ctx_w->include_path = create_string(Nan::Get(options, Nan::New("includePaths").ToLocalChecked())); - ctx_w->out_file = create_string(Nan::Get(options, Nan::New("outFile").ToLocalChecked())); - ctx_w->source_map = create_string(Nan::Get(options, Nan::New("sourceMap").ToLocalChecked())); - ctx_w->source_map_root = create_string(Nan::Get(options, Nan::New("sourceMapRoot").ToLocalChecked())); + indent_width_len, + indent_type_len == 1 ? '\t' : ' ' + ).c_str()); + + napi_value propertyLinefeed; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "linefeed", &propertyLinefeed)); + napi_value propertyIncludePaths; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "includePaths", &propertyIncludePaths)); + napi_value propertyOutFile; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "outFile", &propertyOutFile)); + napi_value propertySourceMap; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "sourceMap", &propertySourceMap)); + napi_value propertySourceMapRoot; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "sourceMapRoot", &propertySourceMapRoot)); + + ctx_w->linefeed = create_string(e, propertyLinefeed); + ctx_w->include_path = create_string(e, propertyIncludePaths); + ctx_w->out_file = create_string(e, propertyOutFile); + ctx_w->source_map = create_string(e, propertySourceMap); + ctx_w->source_map_root = create_string(e, propertySourceMapRoot); + + napi_value propertyStyle; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "style", &propertyStyle)); + napi_value propertyIdentedSyntax; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "indentedSyntax", &propertyIdentedSyntax)); + napi_value propertySourceComments; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "sourceComments", &propertySourceComments)); + napi_value propertyOmitSourceMapUrl; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "omitSourceMapUrl", &propertyOmitSourceMapUrl)); + napi_value propertySourceMapEmbed; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "sourceMapEmbed", &propertySourceMapEmbed)); + napi_value propertySourceMapContents; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "sourceMapContents", &propertySourceMapContents)); + napi_value propertyPrecision; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "precision", &propertyPrecision)); + napi_value propertyImporter; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "importer", &propertyImporter)); + napi_value propertyFunctions; + CHECK_NAPI_RESULT(napi_get_named_property(e, options, "functions", &propertyFunctions)); + + int32_t styleVal; + CHECK_NAPI_RESULT(napi_get_value_int32(e, propertyStyle, &styleVal)); + bool indentedSyntaxVal; + CHECK_NAPI_RESULT(napi_coerce_to_bool(e, propertyIdentedSyntax, &propertyIdentedSyntax)); + CHECK_NAPI_RESULT(napi_get_value_bool(e, propertyIdentedSyntax, &indentedSyntaxVal)); + bool sourceCommentsVal; + CHECK_NAPI_RESULT(napi_coerce_to_bool(e, propertySourceComments, &propertySourceComments)); + CHECK_NAPI_RESULT(napi_get_value_bool(e, propertySourceComments, &sourceCommentsVal)); + bool omitSourceMapUrlVal; + CHECK_NAPI_RESULT(napi_coerce_to_bool(e, propertyOmitSourceMapUrl, &propertyOmitSourceMapUrl)); + CHECK_NAPI_RESULT(napi_get_value_bool(e, propertyOmitSourceMapUrl, &omitSourceMapUrlVal)); + bool sourceMapEmbedVal; + CHECK_NAPI_RESULT(napi_coerce_to_bool(e, propertySourceMapEmbed, &propertySourceMapEmbed)); + CHECK_NAPI_RESULT(napi_get_value_bool(e, propertySourceMapEmbed, &sourceMapEmbedVal)); + bool sourceMapContentsVal; + CHECK_NAPI_RESULT(napi_coerce_to_bool(e, propertySourceMapContents, &propertySourceMapContents)); + CHECK_NAPI_RESULT(napi_get_value_bool(e, propertySourceMapContents, &sourceMapContentsVal)); + int32_t precisionVal; + CHECK_NAPI_RESULT(napi_get_value_int32(e, propertyPrecision, &precisionVal)); sass_option_set_output_path(sass_options, ctx_w->out_file); - sass_option_set_output_style(sass_options, (Sass_Output_Style)Nan::To(Nan::Get(options, Nan::New("style").ToLocalChecked()).ToLocalChecked()).FromJust()); - sass_option_set_is_indented_syntax_src(sass_options, Nan::To(Nan::Get(options, Nan::New("indentedSyntax").ToLocalChecked()).ToLocalChecked()).FromJust()); - sass_option_set_source_comments(sass_options, Nan::To(Nan::Get(options, Nan::New("sourceComments").ToLocalChecked()).ToLocalChecked()).FromJust()); - sass_option_set_omit_source_map_url(sass_options, Nan::To(Nan::Get(options, Nan::New("omitSourceMapUrl").ToLocalChecked()).ToLocalChecked()).FromJust()); - sass_option_set_source_map_embed(sass_options, Nan::To(Nan::Get(options, Nan::New("sourceMapEmbed").ToLocalChecked()).ToLocalChecked()).FromJust()); - sass_option_set_source_map_contents(sass_options, Nan::To(Nan::Get(options, Nan::New("sourceMapContents").ToLocalChecked()).ToLocalChecked()).FromJust()); + sass_option_set_output_style(sass_options, (Sass_Output_Style)styleVal); + sass_option_set_is_indented_syntax_src(sass_options, indentedSyntaxVal); + sass_option_set_source_comments(sass_options, sourceCommentsVal); + sass_option_set_omit_source_map_url(sass_options, omitSourceMapUrlVal); + sass_option_set_source_map_embed(sass_options, sourceMapEmbedVal); + sass_option_set_source_map_contents(sass_options, sourceMapContentsVal); sass_option_set_source_map_file(sass_options, ctx_w->source_map); sass_option_set_source_map_root(sass_options, ctx_w->source_map_root); sass_option_set_include_path(sass_options, ctx_w->include_path); - sass_option_set_precision(sass_options, Nan::To(Nan::Get(options, Nan::New("precision").ToLocalChecked()).ToLocalChecked()).FromJust()); + sass_option_set_precision(sass_options, precisionVal); sass_option_set_indent(sass_options, ctx_w->indent); sass_option_set_linefeed(sass_options, ctx_w->linefeed); - v8::Local importer_callback = Nan::Get(options, Nan::New("importer").ToLocalChecked()).ToLocalChecked(); + CHECK_NAPI_RESULT(napi_typeof(e, propertyImporter, &t)); - if (importer_callback->IsFunction()) { - v8::Local importer = importer_callback.As(); - - CustomImporterBridge *bridge = new CustomImporterBridge(importer, ctx_w->is_sync); + if (t == napi_function) { + CustomImporterBridge *bridge = new CustomImporterBridge(e, propertyImporter, ctx_w->is_sync); ctx_w->importer_bridges.push_back(bridge); Sass_Importer_List c_importers = sass_make_importer_list(1); @@ -127,38 +191,51 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp sass_option_set_c_importers(sass_options, c_importers); } - else if (importer_callback->IsArray()) { - v8::Local importers = importer_callback.As(); - Sass_Importer_List c_importers = sass_make_importer_list(importers->Length()); + else { + bool isArray; + CHECK_NAPI_RESULT(napi_is_array(e, propertyImporter, &isArray)); - for (size_t i = 0; i < importers->Length(); ++i) { - v8::Local callback = v8::Local::Cast(Nan::Get(importers, static_cast(i)).ToLocalChecked()); + if (isArray) { + uint32_t len; + CHECK_NAPI_RESULT(napi_get_array_length(e, propertyImporter, &len)); - CustomImporterBridge *bridge = new CustomImporterBridge(callback, ctx_w->is_sync); - ctx_w->importer_bridges.push_back(bridge); + Sass_Importer_List c_importers = sass_make_importer_list(len); - c_importers[i] = sass_make_importer(sass_importer, importers->Length() - i - 1, bridge); - } + for (uint32_t i = 0; i < len; ++i) { + napi_value callback; + CHECK_NAPI_RESULT(napi_get_element(e, propertyImporter, i, &callback)); - sass_option_set_c_importers(sass_options, c_importers); + CustomImporterBridge *bridge = new CustomImporterBridge(e, callback, ctx_w->is_sync); + ctx_w->importer_bridges.push_back(bridge); + + c_importers[i] = sass_make_importer(sass_importer, len - i - 1, bridge); + } + + sass_option_set_c_importers(sass_options, c_importers); + } } - v8::Local custom_functions = Nan::Get(options, Nan::New("functions").ToLocalChecked()).ToLocalChecked(); + CHECK_NAPI_RESULT(napi_typeof(e, propertyFunctions, &t)); + + if (t == napi_object) { + // TODO: this should be napi_get_own_propertynames + napi_value signatures; + CHECK_NAPI_RESULT(napi_get_property_names(e, propertyFunctions, &signatures)); + uint32_t num_signatures; + CHECK_NAPI_RESULT(napi_get_array_length(e, signatures, &num_signatures)); - if (custom_functions->IsObject()) { - v8::Local functions = custom_functions.As(); - v8::Local signatures = Nan::GetOwnPropertyNames(functions).ToLocalChecked(); - unsigned num_signatures = signatures->Length(); Sass_Function_List fn_list = sass_make_function_list(num_signatures); - for (unsigned i = 0; i < num_signatures; i++) { - v8::Local signature = v8::Local::Cast(Nan::Get(signatures, Nan::New(i)).ToLocalChecked()); - v8::Local callback = v8::Local::Cast(Nan::Get(functions, signature).ToLocalChecked()); + for (uint32_t i = 0; i < num_signatures; i++) { + napi_value signature; + CHECK_NAPI_RESULT(napi_get_element(e, signatures, i, &signature)); + napi_value callback; + CHECK_NAPI_RESULT(napi_get_property(e, propertyFunctions, signature, &callback)); - CustomFunctionBridge *bridge = new CustomFunctionBridge(callback, ctx_w->is_sync); + CustomFunctionBridge *bridge = new CustomFunctionBridge(e, callback, ctx_w->is_sync); ctx_w->function_bridges.push_back(bridge); - char* sig = create_string(signature); + char* sig = create_string(e, signature); Sass_Function_Entry fn = sass_make_function(sig, sass_custom_function, bridge); free(sig); sass_function_set_list_entry(fn_list, i, fn); @@ -169,79 +246,93 @@ int ExtractOptions(v8::Local options, void* cptr, sass_context_wrapp return 0; } -void GetStats(sass_context_wrapper* ctx_w, Sass_Context* ctx) { - Nan::HandleScope scope; +void GetStats(napi_env env, sass_context_wrapper* ctx_w, Sass_Context* ctx) { + // TODO: Enable napi_close_handle_scope during a pending exception state. + //Napi::HandleScope scope; char** included_files = sass_context_get_included_files(ctx); - v8::Local arr = Nan::New(); + napi_value arr; + CHECK_NAPI_RESULT(napi_create_array(env, &arr)); if (included_files) { for (int i = 0; included_files[i] != nullptr; ++i) { - Nan::Set(arr, i, Nan::New(included_files[i]).ToLocalChecked()); + const char* s = included_files[i]; + int len = (int)strlen(s); + napi_value str; + CHECK_NAPI_RESULT(napi_create_string_utf8(env, s, len, &str)); + CHECK_NAPI_RESULT(napi_set_element(env, arr, i, str)); } } - v8::Local result = Nan::New(ctx_w->result); - assert(result->IsObject()); - - v8::Local stats = Nan::Get( - result, - Nan::New("stats").ToLocalChecked() - ).ToLocalChecked(); - if (stats->IsObject()) { - Nan::Set( - stats.As(), - Nan::New("includedFiles").ToLocalChecked(), - arr - ); + napi_value result; + CHECK_NAPI_RESULT(napi_get_reference_value(env, ctx_w->result, &result)); + assert(result != nullptr); + + napi_value propertyStats; + CHECK_NAPI_RESULT(napi_get_named_property(env, result, "stats", &propertyStats)); + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, propertyStats, &t)); + + if (t == napi_object) { + CHECK_NAPI_RESULT(napi_set_named_property(env, propertyStats, "includedFiles", arr)); } else { - Nan::ThrowTypeError("\"result.stats\" element is not an object"); + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "\"result.stats\" element is not an object")); } } -int GetResult(sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = false) { - Nan::HandleScope scope; - v8::Local result; - +int GetResult(napi_env env, sass_context_wrapper* ctx_w, Sass_Context* ctx, bool is_sync = false) { + // TODO: Enable napi_close_handle_scope during a pending exception state. + //Napi::HandleScope scope(env); int status = sass_context_get_error_status(ctx); - result = Nan::New(ctx_w->result); - assert(result->IsObject()); + napi_value result; + CHECK_NAPI_RESULT(napi_get_reference_value(env, ctx_w->result, &result)); + assert(result != nullptr); if (status == 0) { const char* css = sass_context_get_output_string(ctx); const char* map = sass_context_get_source_map_string(ctx); + size_t css_len = strlen(css); - Nan::Set(result, Nan::New("css").ToLocalChecked(), Nan::CopyBuffer(css, static_cast(strlen(css))).ToLocalChecked()); + napi_value cssBuffer; + CHECK_NAPI_RESULT(napi_create_buffer_copy(env, css_len, css, NULL, &cssBuffer)); + CHECK_NAPI_RESULT(napi_set_named_property(env, result, "css", cssBuffer)); - GetStats(ctx_w, ctx); + GetStats(env, ctx_w, ctx); if (map) { - Nan::Set(result, Nan::New("map").ToLocalChecked(), Nan::CopyBuffer(map, static_cast(strlen(map))).ToLocalChecked()); + size_t map_len = strlen(map); + napi_value mapBuffer; + CHECK_NAPI_RESULT(napi_create_buffer_copy(env, map_len, map, NULL, &mapBuffer)); + CHECK_NAPI_RESULT(napi_set_named_property(env, result, "map", mapBuffer)); } } else if (is_sync) { - Nan::Set(result, Nan::New("error").ToLocalChecked(), Nan::New(sass_context_get_error_json(ctx)).ToLocalChecked()); + const char* err = sass_context_get_error_json(ctx); + size_t err_len = strlen(err); + napi_value str; + CHECK_NAPI_RESULT(napi_create_string_utf8(env, err, err_len, &str)); + CHECK_NAPI_RESULT(napi_set_named_property(env, result, "error", str)); } return status; } -void PerformCall(sass_context_wrapper* ctx_w, Nan::Callback* callback, int argc, v8::Local argv[]) { - if (ctx_w->is_sync) { - Nan::Call(*callback, argc, argv); - } else { - callback->Call(argc, argv, ctx_w->async_resource); - } -} +// ASYNC +// void PerformCall(sass_context_wrapper* ctx_w, Nan::Callback* callback, int argc, v8::Local argv[]) { +// if (ctx_w->is_sync) { +// Nan::Call(*callback, argc, argv); +// } else { +// callback->Call(argc, argv, ctx_w->async_resource); +// } +// } void MakeCallback(uv_work_t* req) { - Nan::HandleScope scope; - - Nan::TryCatch try_catch; sass_context_wrapper* ctx_w = static_cast(req->data); struct Sass_Context* ctx; + Napi::HandleScope scope(ctx_w->env); + if (ctx_w->dctx) { ctx = sass_data_context_get_context(ctx_w->dctx); } @@ -249,110 +340,191 @@ void MakeCallback(uv_work_t* req) { ctx = sass_file_context_get_context(ctx_w->fctx); } - int status = GetResult(ctx_w, ctx); + int status = GetResult(ctx_w->env, ctx_w, ctx); + + napi_value global; + CHECK_NAPI_RESULT(napi_get_global(ctx_w->env, &global)); if (status == 0 && ctx_w->success_callback) { // if no error, do callback(null, result) - PerformCall(ctx_w, ctx_w->success_callback, 0, 0); + napi_value success_cb; + CHECK_NAPI_RESULT(napi_get_reference_value(ctx_w->env, ctx_w->success_callback, &success_cb)); + assert(success_cb != nullptr); + + napi_value unused; + CHECK_NAPI_RESULT(napi_make_callback(ctx_w->env, nullptr, global, success_cb, 0, nullptr, &unused)); } else if (ctx_w->error_callback) { // if error, do callback(error) const char* err = sass_context_get_error_json(ctx); - v8::Local argv[] = { - Nan::New(err).ToLocalChecked() + int len = (int)strlen(err); + napi_value str; + CHECK_NAPI_RESULT(napi_create_string_utf8(ctx_w->env, err, len, &str)); + + napi_value argv[] = { + str }; - PerformCall(ctx_w, ctx_w->error_callback, 1, argv); + + napi_value error_cb; + CHECK_NAPI_RESULT(napi_get_reference_value(ctx_w->env, ctx_w->error_callback, &error_cb)); + assert(error_cb != nullptr); + + napi_value unused; + CHECK_NAPI_RESULT(napi_make_callback(ctx_w->env, nullptr, global, error_cb, 1, argv, &unused)); } - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); + + bool isPending; + CHECK_NAPI_RESULT(napi_is_exception_pending(ctx_w->env, &isPending)); + if (isPending) { + napi_value result; + CHECK_NAPI_RESULT(napi_get_and_clear_last_exception(ctx_w->env, &result)); + CHECK_NAPI_RESULT(napi_fatal_exception(ctx_w->env, result)); } sass_free_context_wrapper(ctx_w); } -NAN_METHOD(render) { +napi_value render(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value options; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &options, nullptr, nullptr)); + + napi_value propertyData; + CHECK_NAPI_RESULT(napi_get_named_property(env, options, "data", &propertyData)); + char* source_string = create_string(env, propertyData); - v8::Local options = Nan::To(info[0]).ToLocalChecked(); - char* source_string = create_string(Nan::Get(options, Nan::New("data").ToLocalChecked())); struct Sass_Data_Context* dctx = sass_make_data_context(source_string); - sass_context_wrapper* ctx_w = sass_make_context_wrapper(); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(env); - ctx_w->async_resource = new Nan::AsyncResource("node-sass:sass_context_wrapper:render"); + // ASYNC + // ctx_w->async_resource = new Nan::AsyncResource("node-sass:sass_context_wrapper:render"); - if (ExtractOptions(options, dctx, ctx_w, false, false) >= 0) { + if (ExtractOptions(env, options, dctx, ctx_w, false, false) >= 0) { int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback); assert(status == 0); } + + napi_value ret; + CHECK_NAPI_RESULT(napi_get_null(env, &ret)); + return ret; } -NAN_METHOD(render_sync) { +napi_value render_sync(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value options; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &options, nullptr, nullptr)); + + napi_value propertyData; + CHECK_NAPI_RESULT(napi_get_named_property(env, options, "data", &propertyData)); + char* source_string = create_string(env, propertyData); - v8::Local options = Nan::To(info[0]).ToLocalChecked(); - char* source_string = create_string(Nan::Get(options, Nan::New("data").ToLocalChecked())); struct Sass_Data_Context* dctx = sass_make_data_context(source_string); struct Sass_Context* ctx = sass_data_context_get_context(dctx); - sass_context_wrapper* ctx_w = sass_make_context_wrapper(); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(env); int result = -1; - if ((result = ExtractOptions(options, dctx, ctx_w, false, true)) >= 0) { + if ((result = ExtractOptions(env, options, dctx, ctx_w, false, true)) >= 0) { compile_data(dctx); - result = GetResult(ctx_w, ctx, true); + result = GetResult(env, ctx_w, ctx, true); } sass_free_context_wrapper(ctx_w); - info.GetReturnValue().Set(result == 0); + bool isExceptionPending; + CHECK_NAPI_RESULT(napi_is_exception_pending(env, &isExceptionPending)); + if (!isExceptionPending) { + napi_value boolResult; + CHECK_NAPI_RESULT(napi_get_boolean(env, result == 0, &boolResult)); + return boolResult; + } + + return nullptr; } -NAN_METHOD(render_file) { +napi_value render_file(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value options; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &options, nullptr, nullptr)); + + napi_value propertyFile; + CHECK_NAPI_RESULT(napi_get_named_property(env, options, "file", &propertyFile)); + char* input_path = create_string(env, propertyFile); - v8::Local options = Nan::To(info[0]).ToLocalChecked(); - char* input_path = create_string(Nan::Get(options, Nan::New("file").ToLocalChecked())); struct Sass_File_Context* fctx = sass_make_file_context(input_path); - sass_context_wrapper* ctx_w = sass_make_context_wrapper(); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(env); - ctx_w->async_resource = new Nan::AsyncResource("node-sass:sass_context_wrapper:render_file"); + // ASYNC + // ctx_w->async_resource = new Nan::AsyncResource("node-sass:sass_context_wrapper:render_file"); - if (ExtractOptions(options, fctx, ctx_w, true, false) >= 0) { + if (ExtractOptions(env, options, fctx, ctx_w, true, false) >= 0) { int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback); assert(status == 0); } + + napi_value ret; + CHECK_NAPI_RESULT(napi_get_null(env, &ret)); + return ret; } -NAN_METHOD(render_file_sync) { +napi_value render_file_sync(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value options; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &options, nullptr, nullptr)); + + napi_value propertyFile; + CHECK_NAPI_RESULT(napi_get_named_property(env, options, "file", &propertyFile)); + char* input_path = create_string(env, propertyFile); - v8::Local options = Nan::To(info[0]).ToLocalChecked(); - char* input_path = create_string(Nan::Get(options, Nan::New("file").ToLocalChecked())); struct Sass_File_Context* fctx = sass_make_file_context(input_path); struct Sass_Context* ctx = sass_file_context_get_context(fctx); - sass_context_wrapper* ctx_w = sass_make_context_wrapper(); + sass_context_wrapper* ctx_w = sass_make_context_wrapper(env); int result = -1; - if ((result = ExtractOptions(options, fctx, ctx_w, true, true)) >= 0) { + if ((result = ExtractOptions(env, options, fctx, ctx_w, true, true)) >= 0) { compile_file(fctx); - result = GetResult(ctx_w, ctx, true); + result = GetResult(env, ctx_w, ctx, true); }; free(input_path); sass_free_context_wrapper(ctx_w); - info.GetReturnValue().Set(result == 0); + napi_value b; + CHECK_NAPI_RESULT(napi_get_boolean(env, result == 0, &b)); + return b; } -NAN_METHOD(libsass_version) { - info.GetReturnValue().Set(Nan::New(libsass_version()).ToLocalChecked()); +napi_value libsass_version(napi_env env, napi_callback_info info) { + const char* ver = libsass_version(); + int len = (int)strlen(ver); + napi_value str; + + CHECK_NAPI_RESULT(napi_create_string_utf8(env, ver, len, &str)); + return str; } -NAN_MODULE_INIT(RegisterModule) { - Nan::SetMethod(target, "render", render); - Nan::SetMethod(target, "renderSync", render_sync); - Nan::SetMethod(target, "renderFile", render_file); - Nan::SetMethod(target, "renderFileSync", render_file_sync); - Nan::SetMethod(target, "libsassVersion", libsass_version); - SassTypes::Factory::initExports(target); +napi_value Init(napi_env env, napi_value target) { + napi_value functionRender; + CHECK_NAPI_RESULT(napi_create_function(env, "render", NAPI_AUTO_LENGTH, render, nullptr, &functionRender)); + napi_value functionRenderSync; + CHECK_NAPI_RESULT(napi_create_function(env, "renderSync", NAPI_AUTO_LENGTH, render_sync, nullptr, &functionRenderSync)); + napi_value functionRenderFile; + CHECK_NAPI_RESULT(napi_create_function(env, "renderFile", NAPI_AUTO_LENGTH, render_file, nullptr, &functionRenderFile)); + napi_value functionRenderFileSync; + CHECK_NAPI_RESULT(napi_create_function(env, "renderFileSync", NAPI_AUTO_LENGTH, render_file_sync, nullptr, &functionRenderFileSync)); + napi_value functionLibsassVersion; + CHECK_NAPI_RESULT(napi_create_function(env, "libsassVersion", NAPI_AUTO_LENGTH, libsass_version, nullptr, &functionLibsassVersion)); + + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "render", functionRender)); + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "renderSync", functionRenderSync)); + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "renderFile", functionRenderFile)); + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "renderFileSync", functionRenderFileSync)); + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "libsassVersion", functionLibsassVersion)); + + SassTypes::Factory::initExports(env, target); + return target; } -NODE_MODULE(binding, RegisterModule); +NAPI_MODULE(binding, Init) diff --git a/src/callback_bridge.h b/src/callback_bridge.h index 25f62e140..c965fcaa7 100644 --- a/src/callback_bridge.h +++ b/src/callback_bridge.h @@ -2,30 +2,32 @@ #define CALLBACK_BRIDGE_H #include -#include #include #include - -#define COMMA , +#include "common.h" +#include template class CallbackBridge { public: - CallbackBridge(v8::Local, bool); + CallbackBridge(napi_env, napi_value, bool); virtual ~CallbackBridge(); // Executes the callback T operator()(std::vector); + // Needed for napi_wrap + napi_value NewInstance(napi_env env); + protected: // We will expose a bridge object to the JS callback that wraps this instance so we don't loose context. // This is the V8 constructor for such objects. - static Nan::MaybeLocal get_wrapper_constructor(); + static napi_ref get_wrapper_constructor(napi_env env); static void async_gone(uv_handle_t *handle); - static NAN_METHOD(New); - static NAN_METHOD(ReturnCallback); - static Nan::Persistent wrapper_constructor; - Nan::Persistent wrapper; + static napi_value New(napi_env env, napi_callback_info info); + static napi_value ReturnCallback(napi_env env, napi_callback_info info); + static napi_ref wrapper_constructor; + napi_ref wrapper; // The callback that will get called in the main thread after the worker thread used for the sass // compilation step makes a call to uv_async_send() @@ -34,13 +36,16 @@ class CallbackBridge { // The V8 values sent to our ReturnCallback must be read on the main thread not the sass worker thread. // This gives a chance to specialized subclasses to transform those values into whatever makes sense to // sass before we resume the worker thread. - virtual T post_process_return_value(v8::Local) const =0; + virtual T post_process_return_value(napi_env, napi_value) const = 0; + + virtual std::vector pre_process_args(napi_env, std::vector) const = 0; - virtual std::vector> pre_process_args(std::vector) const =0; + // ASYNC + // Nan::AsyncResource* async_resource; - Nan::Callback* callback; - Nan::AsyncResource* async_resource; + napi_env e; + napi_ref callback; bool is_sync; uv_mutex_t cv_mutex; @@ -52,45 +57,57 @@ class CallbackBridge { }; template -Nan::Persistent CallbackBridge::wrapper_constructor; +napi_ref CallbackBridge::wrapper_constructor = nullptr; + +template +napi_value CallbackBridge::NewInstance(napi_env env) { + Napi::EscapableHandleScope scope(env); + + napi_value instance; + napi_value constructorHandle; + CHECK_NAPI_RESULT(napi_get_reference_value(env, get_wrapper_constructor(env), &constructorHandle)); + CHECK_NAPI_RESULT(napi_new_instance(env, constructorHandle, 0, nullptr, &instance)); + + return scope.Escape(instance); +} template -CallbackBridge::CallbackBridge(v8::Local callback, bool is_sync) : callback(new Nan::Callback(callback)), is_sync(is_sync) { +CallbackBridge::CallbackBridge(napi_env env, napi_value callback, bool is_sync) : e(env), is_sync(is_sync) { /* * This is invoked from the main JavaScript thread. * V8 context is available. */ - Nan::HandleScope scope; + Napi::HandleScope scope(this->e); uv_mutex_init(&this->cv_mutex); uv_cond_init(&this->condition_variable); if (!is_sync) { this->async = new uv_async_t; this->async->data = (void*) this; uv_async_init(uv_default_loop(), this->async, (uv_async_cb) dispatched_async_uv_callback); - this->async_resource = new Nan::AsyncResource("node-sass:CallbackBridge"); } - v8::Local func = CallbackBridge::get_wrapper_constructor().ToLocalChecked(); - wrapper.Reset(Nan::NewInstance(func).ToLocalChecked()); - Nan::SetInternalFieldPointer(Nan::New(wrapper), 0, this); + CHECK_NAPI_RESULT(napi_create_reference(env, callback, 1, &this->callback)); + + napi_value instance = CallbackBridge::NewInstance(env); + CHECK_NAPI_RESULT(napi_wrap(env, instance, this, nullptr, nullptr, nullptr)); + CHECK_NAPI_RESULT(napi_create_reference(env, instance, 1, &this->wrapper)); } template CallbackBridge::~CallbackBridge() { - delete this->callback; - this->wrapper.Reset(); + CHECK_NAPI_RESULT(napi_delete_reference(e, this->callback)); + CHECK_NAPI_RESULT(napi_delete_reference(e, this->wrapper)); + uv_cond_destroy(&this->condition_variable); uv_mutex_destroy(&this->cv_mutex); if (!is_sync) { uv_close((uv_handle_t*)this->async, &async_gone); - delete this->async_resource; } } template T CallbackBridge::operator()(std::vector argv) { - // argv.push_back(wrapper); if (this->is_sync) { /* * This is invoked from the main JavaScript thread. @@ -100,18 +117,32 @@ T CallbackBridge::operator()(std::vector argv) { * from types invoked by pre_process_args() and * post_process_args(). */ - Nan::HandleScope scope; - Nan::TryCatch try_catch; - std::vector> argv_v8 = pre_process_args(argv); - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); + Napi::HandleScope scope(this->e); + + std::vector argv_v8 = pre_process_args(this->e, argv); + + bool isPending; + CHECK_NAPI_RESULT(napi_is_exception_pending(this->e, &isPending)); + + if (isPending) { + CHECK_NAPI_RESULT(napi_throw_error(this->e, nullptr, "Error processing arguments")); + // This should be a FatalException but we still have to return something, this value might be uninitialized + return this->return_value; } - argv_v8.push_back(Nan::New(wrapper)); + napi_value _this; + CHECK_NAPI_RESULT(napi_get_reference_value(this->e, this->wrapper, &_this)); + argv_v8.push_back(_this); + + napi_value cb; + CHECK_NAPI_RESULT(napi_get_reference_value(this->e, this->callback, &cb)); + assert(cb != nullptr); + + napi_value result; + // TODO: Is receiver set correctly ? + CHECK_NAPI_RESULT(napi_make_callback(this->e, nullptr, _this, cb, argv_v8.size(), &argv_v8[0], &result)); - return this->post_process_return_value( - Nan::Call(*this->callback, argv_v8.size(), &argv_v8[0]).ToLocalChecked() - ); + return this->post_process_return_value(this->e, result); } else { /* * This is invoked from the worker thread. @@ -153,24 +184,46 @@ void CallbackBridge::dispatched_async_uv_callback(uv_async_t *req) { * from types invoked by pre_process_args() and * post_process_args(). */ - Nan::HandleScope scope; - Nan::TryCatch try_catch; - - std::vector> argv_v8 = bridge->pre_process_args(bridge->argv); - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); + Napi::HandleScope scope(bridge->e); + std::vector argv_v8 = bridge->pre_process_args(bridge->e, bridge->argv); + bool isPending; + + CHECK_NAPI_RESULT(napi_is_exception_pending(bridge->e, &isPending)); + if (isPending) { + CHECK_NAPI_RESULT(napi_throw_error(bridge->e, nullptr, "Error processing arguments")); + // This should be a FatalException + return; } - argv_v8.push_back(Nan::New(bridge->wrapper)); - - bridge->callback->Call(argv_v8.size(), &argv_v8[0], bridge->async_resource); - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); + napi_value _this; + CHECK_NAPI_RESULT(napi_get_reference_value(bridge->e, bridge->wrapper, &_this)); + argv_v8.push_back(_this); + + napi_value cb; + CHECK_NAPI_RESULT(napi_get_reference_value(bridge->e, bridge->callback, &cb)); + assert(cb != nullptr); + + napi_value result; + // TODO: Is receiver set correctly ? + CHECK_NAPI_RESULT(napi_make_callback(bridge->e, nullptr, _this, cb, argv_v8.size(), &argv_v8[0], &result)); + CHECK_NAPI_RESULT(napi_is_exception_pending(bridge->e, &isPending)); + if (isPending) { + CHECK_NAPI_RESULT(napi_throw_error(bridge->e, nullptr, "Error thrown in callback")); + // This should be a FatalException + return; } } template -NAN_METHOD(CallbackBridge::ReturnCallback) { +napi_value CallbackBridge::ReturnCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &arg, &_this, nullptr)); + + void* unwrapped; + CHECK_NAPI_RESULT(napi_unwrap(env, _this, &unwrapped)); + CallbackBridge* bridge = static_cast(unwrapped); /* * Callback function invoked by the user code. @@ -179,10 +232,7 @@ NAN_METHOD(CallbackBridge::ReturnCallback) { * * Implicit Local<> handle scope created by NAN_METHOD(.) */ - CallbackBridge* bridge = static_cast*>(Nan::GetInternalFieldPointer(info.This(), 0)); - Nan::TryCatch try_catch; - - bridge->return_value = bridge->post_process_return_value(info[0]); + bridge->return_value = bridge->post_process_return_value(env, arg); { uv_mutex_lock(&bridge->cv_mutex); @@ -192,32 +242,38 @@ NAN_METHOD(CallbackBridge::ReturnCallback) { uv_cond_broadcast(&bridge->condition_variable); - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); + bool isPending; + CHECK_NAPI_RESULT(napi_is_exception_pending(env, &isPending)); + + if (isPending) { + napi_value result; + CHECK_NAPI_RESULT(napi_get_and_clear_last_exception(env, &result)); + CHECK_NAPI_RESULT(napi_fatal_exception(env, result)); } + + return nullptr; } template -Nan::MaybeLocal CallbackBridge::get_wrapper_constructor() { - /* Uses handle scope created in the CallbackBridge constructor */ - if (wrapper_constructor.IsEmpty()) { - v8::Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New("CallbackBridge").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - Nan::SetPrototypeTemplate(tpl, "success", - Nan::New(ReturnCallback) - ); - - wrapper_constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked()); - } +napi_ref CallbackBridge::get_wrapper_constructor(napi_env env) { + // TODO: cache wrapper_constructor + + napi_property_descriptor methods[] = { + { "success", nullptr, CallbackBridge::ReturnCallback }, + }; + + napi_value ctor; + CHECK_NAPI_RESULT(napi_define_class(env, "CallbackBridge", NAPI_AUTO_LENGTH, CallbackBridge::New, nullptr, 1, methods, &ctor)); + CHECK_NAPI_RESULT(napi_create_reference(env, ctor, 1, &wrapper_constructor)); - return Nan::New(wrapper_constructor); + return wrapper_constructor; } template -NAN_METHOD(CallbackBridge::New) { - info.GetReturnValue().Set(info.This()); +napi_value CallbackBridge::New(napi_env env, napi_callback_info info) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + return _this; } template diff --git a/src/common.h b/src/common.h new file mode 100644 index 000000000..192937fa4 --- /dev/null +++ b/src/common.h @@ -0,0 +1,11 @@ +#ifndef COMMON_H +#define COMMON_H + +#define NAPI_DISABLE_CPP_EXCEPTIONS 1 + +#include + +#define CHECK_NAPI_RESULT(condition) \ + do { napi_status status = (condition); assert(status == napi_ok || status == napi_pending_exception); } while(0) + +#endif // COMMON_H diff --git a/src/create_string.cpp b/src/create_string.cpp index 27a496f7a..536cc9d62 100644 --- a/src/create_string.cpp +++ b/src/create_string.cpp @@ -1,21 +1,29 @@ -#include #include #include #include "create_string.h" +#include -char* create_string(Nan::MaybeLocal maybevalue) { - v8::Local value; +#define CHECK_NAPI_RESULT_RETURN_NULL(condition) do { if((condition) != napi_ok) { return nullptr; } } while(0) - if (maybevalue.ToLocal(&value)) { - if (value->IsNull() || !value->IsString()) { - return 0; - } - } else { - return 0; +char* create_string(napi_env e, napi_value v) { + napi_valuetype t; + CHECK_NAPI_RESULT_RETURN_NULL(napi_typeof(e, v, &t)); + + if (t != napi_string) { + return nullptr; + } + + size_t len; + CHECK_NAPI_RESULT_RETURN_NULL(napi_get_value_string_utf8(e, v, NULL, 0, &len)); + + char* str = (char *)malloc(len + 1); + size_t written; + CHECK_NAPI_RESULT_RETURN_NULL(napi_get_value_string_utf8(e, v, str, len + 1, &written)); + + if (len != written) { + free(str); + return nullptr; } - Nan::Utf8String string(value); - char *str = (char *)malloc(string.length() + 1); - strcpy(str, *string); return str; } diff --git a/src/create_string.h b/src/create_string.h index 03c7c927d..234b8f9b0 100644 --- a/src/create_string.h +++ b/src/create_string.h @@ -1,8 +1,10 @@ #ifndef CREATE_STRING_H #define CREATE_STRING_H -#include +#define NAPI_DISABLE_CPP_EXCEPTIONS 1 -char* create_string(Nan::MaybeLocal); +#include + +char* create_string(napi_env e, napi_value v); #endif diff --git a/src/custom_function_bridge.cpp b/src/custom_function_bridge.cpp index f27c69545..6066b6f90 100644 --- a/src/custom_function_bridge.cpp +++ b/src/custom_function_bridge.cpp @@ -1,11 +1,10 @@ -#include #include #include "custom_function_bridge.h" #include "sass_types/factory.h" #include "sass_types/value.h" -Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local _val) const { - SassTypes::Value *value = SassTypes::Factory::unwrap(_val); +Sass_Value* CustomFunctionBridge::post_process_return_value(napi_env env, napi_value v) const { + SassTypes::Value *value = SassTypes::Factory::unwrap(env, v); if (value) { return value->get_sass_value(); } else { @@ -13,14 +12,14 @@ Sass_Value* CustomFunctionBridge::post_process_return_value(v8::Local } } -std::vector> CustomFunctionBridge::pre_process_args(std::vector in) const { - std::vector> argv = std::vector>(); +std::vector CustomFunctionBridge::pre_process_args(napi_env env, std::vector in) const { + std::vector argv; for (void* value : in) { Sass_Value* x = static_cast(value); - SassTypes::Value* y = SassTypes::Factory::create(x); + SassTypes::Value* y = SassTypes::Factory::create(env, x); - argv.push_back(y->get_js_object()); + argv.push_back(y->get_js_object(env)); } return argv; diff --git a/src/custom_function_bridge.h b/src/custom_function_bridge.h index 99c83ead3..5f375ee64 100644 --- a/src/custom_function_bridge.h +++ b/src/custom_function_bridge.h @@ -1,18 +1,17 @@ #ifndef CUSTOM_FUNCTION_BRIDGE_H #define CUSTOM_FUNCTION_BRIDGE_H -#include #include #include #include "callback_bridge.h" class CustomFunctionBridge : public CallbackBridge { public: - CustomFunctionBridge(v8::Local cb, bool is_sync) : CallbackBridge(cb, is_sync) {} + CustomFunctionBridge(napi_env env, napi_value cb, bool is_sync) : CallbackBridge(env, cb, is_sync) {} private: - Sass_Value* post_process_return_value(v8::Local) const; - std::vector> pre_process_args(std::vector) const; + Sass_Value* post_process_return_value(napi_env, napi_value) const; + std::vector pre_process_args(napi_env, std::vector) const; }; #endif diff --git a/src/custom_importer_bridge.cpp b/src/custom_importer_bridge.cpp index 38d737c26..cf8324993 100644 --- a/src/custom_importer_bridge.cpp +++ b/src/custom_importer_bridge.cpp @@ -1,102 +1,128 @@ -#include #include #include "custom_importer_bridge.h" #include "create_string.h" -SassImportList CustomImporterBridge::post_process_return_value(v8::Local returned_value) const { +SassImportList CustomImporterBridge::post_process_return_value(napi_env env, napi_value returned_value) const { SassImportList imports = 0; - Nan::HandleScope scope; + Napi::HandleScope scope(env); - if (returned_value->IsArray()) { - v8::Local array = returned_value.As(); + bool isArray; + bool isError; + CHECK_NAPI_RESULT(napi_is_array(env, returned_value, &isArray)); + CHECK_NAPI_RESULT(napi_is_error(env, returned_value, &isError)); - imports = sass_make_import_list(array->Length()); + if (isArray) { + uint32_t length; + CHECK_NAPI_RESULT(napi_get_array_length(env, returned_value, &length)); + imports = sass_make_import_list(length); - for (size_t i = 0; i < array->Length(); ++i) { - v8::Local value = Nan::Get(array, static_cast(i)).ToLocalChecked(); + for (uint32_t i = 0; i < length; ++i) { + napi_value value; + CHECK_NAPI_RESULT(napi_get_element(env, returned_value, i, &value)); - if (!value->IsObject()) { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, value, &t)); + + if (t != napi_object) { auto entry = sass_make_import_entry(0, 0, 0); sass_import_set_error(entry, "returned array must only contain object literals", -1, -1); continue; } - v8::Local object = value.As(); + CHECK_NAPI_RESULT(napi_is_error(env, value, &isError)); - if (value->IsNativeError()) { - char* message = create_string(Nan::Get(object, Nan::New("message").ToLocalChecked())); + if (isError) { + napi_value propertyMessage; + CHECK_NAPI_RESULT(napi_get_named_property(env, value, "message", &propertyMessage)); + char* message = create_string(env, propertyMessage); imports[i] = sass_make_import_entry(0, 0, 0); sass_import_set_error(imports[i], message, -1, -1); free(message); } else { - imports[i] = get_importer_entry(object); + imports[i] = get_importer_entry(env, value); } } } - else if (returned_value->IsNativeError()) { + else if (isError) { imports = sass_make_import_list(1); - v8::Local object = returned_value.As(); - char* message = create_string(Nan::Get(object, Nan::New("message").ToLocalChecked())); + napi_value propertyMessage; + CHECK_NAPI_RESULT(napi_get_named_property(env, returned_value, "message", &propertyMessage)); + + char* message = create_string(env, propertyMessage); imports[0] = sass_make_import_entry(0, 0, 0); sass_import_set_error(imports[0], message, -1, -1); free(message); } - else if (returned_value->IsObject()) { - imports = sass_make_import_list(1); - imports[0] = get_importer_entry(returned_value.As()); + else { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, returned_value, &t)); + + if (t == napi_object) { + imports = sass_make_import_list(1); + imports[0] = get_importer_entry(env, returned_value); + } } return imports; } -Sass_Import* CustomImporterBridge::check_returned_string(Nan::MaybeLocal value, const char *msg) const +Sass_Import* CustomImporterBridge::check_returned_string(napi_env env, napi_value value, const char *msg) const { - v8::Local checked; - if (value.ToLocal(&checked)) { - if (!checked->IsUndefined() && !checked->IsString()) { - goto err; - } else { - return nullptr; - } - } + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, value, &t)); + + if (t != napi_undefined && t != napi_string) { + goto err; + } else { + return nullptr; + } + err: - auto entry = sass_make_import_entry(0, 0, 0); - sass_import_set_error(entry, msg, -1, -1); - return entry; + auto entry = sass_make_import_entry(0, 0, 0); + sass_import_set_error(entry, msg, -1, -1); + return entry; } -Sass_Import* CustomImporterBridge::get_importer_entry(const v8::Local& object) const { - auto returned_file = Nan::Get(object, Nan::New("file").ToLocalChecked()); - auto returned_contents = Nan::Get(object, Nan::New("contents").ToLocalChecked()).ToLocalChecked(); - auto returned_map = Nan::Get(object, Nan::New("map").ToLocalChecked()); +Sass_Import* CustomImporterBridge::get_importer_entry(napi_env env, const napi_value& object) const { + napi_value returned_file; + CHECK_NAPI_RESULT(napi_get_named_property(env, object, "file", &returned_file)); + napi_value returned_contents; + CHECK_NAPI_RESULT(napi_get_named_property(env, object, "contents", &returned_contents)); + napi_value returned_map; + CHECK_NAPI_RESULT(napi_get_named_property(env, object, "map", &returned_map)); + Sass_Import *err; - if ((err = check_returned_string(returned_file, "returned value of `file` must be a string"))) + if ((err = check_returned_string(env, returned_file, "returned value of `file` must be a string"))) return err; - if ((err = check_returned_string(returned_contents, "returned value of `contents` must be a string"))) + if ((err = check_returned_string(env, returned_contents, "returned value of `contents` must be a string"))) return err; - if ((err = check_returned_string(returned_map, "returned value of `returned_map` must be a string"))) + if ((err = check_returned_string(env, returned_map, "returned value of `returned_map` must be a string"))) return err; - char* path = create_string(returned_file); - char* contents = create_string(returned_contents); - char* srcmap = create_string(returned_map); + char* path = create_string(env, returned_file); + char* contents = create_string(env, returned_contents); + char* srcmap = create_string(env, returned_map); return sass_make_import_entry(path, contents, srcmap); } -std::vector> CustomImporterBridge::pre_process_args(std::vector in) const { - std::vector> out; +std::vector CustomImporterBridge::pre_process_args(napi_env env, std::vector in) const { + std::vector out; for (void* ptr : in) { - out.push_back(Nan::New((char const*)ptr).ToLocalChecked()); + const char* s = (const char*)ptr; + int len = (int)strlen(s); + napi_value str; + CHECK_NAPI_RESULT(napi_create_string_utf8(env, s, len, &str)); + out.push_back(str); } return out; diff --git a/src/custom_importer_bridge.h b/src/custom_importer_bridge.h index 0cbd3e6d8..f57c5c0d1 100644 --- a/src/custom_importer_bridge.h +++ b/src/custom_importer_bridge.h @@ -1,7 +1,6 @@ #ifndef CUSTOM_IMPORTER_BRIDGE_H #define CUSTOM_IMPORTER_BRIDGE_H -#include #include #include #include "callback_bridge.h" @@ -10,13 +9,13 @@ typedef Sass_Import_List SassImportList; class CustomImporterBridge : public CallbackBridge { public: - CustomImporterBridge(v8::Local cb, bool is_sync) : CallbackBridge(cb, is_sync) {} + CustomImporterBridge(napi_env env, napi_value cb, bool is_sync) : CallbackBridge(env, cb, is_sync) {} private: - SassImportList post_process_return_value(v8::Local) const; - Sass_Import* check_returned_string(Nan::MaybeLocal value, const char *msg) const; - Sass_Import* get_importer_entry(const v8::Local&) const; - std::vector> pre_process_args(std::vector) const; + SassImportList post_process_return_value(napi_env, napi_value) const; + Sass_Import* check_returned_string(napi_env, napi_value, const char*) const; + Sass_Import* get_importer_entry(napi_env, const napi_value&) const; + std::vector pre_process_args(napi_env, std::vector) const; }; #endif diff --git a/src/libsass.gyp b/src/libsass.gyp index add96e89a..42f72f034 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -102,11 +102,6 @@ ] }] ] - }], - ['OS!="win"', { - 'cflags_cc+': [ - '-std=c++0x' - ] }] ] } diff --git a/src/sass_context_wrapper.cpp b/src/sass_context_wrapper.cpp index aa25c79b0..4bff36281 100644 --- a/src/sass_context_wrapper.cpp +++ b/src/sass_context_wrapper.cpp @@ -22,8 +22,10 @@ extern "C" { sass_compile_file_context(fctx); } - sass_context_wrapper* sass_make_context_wrapper() { - return (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper)); + sass_context_wrapper* sass_make_context_wrapper(napi_env env) { + auto ret = (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper)); + ret->env = env; + return ret; } void sass_free_context_wrapper(sass_context_wrapper* ctx_w) { @@ -33,14 +35,16 @@ extern "C" { else if (ctx_w->fctx) { sass_delete_file_context(ctx_w->fctx); } - if (ctx_w->async_resource) { - delete ctx_w->async_resource; - } - - delete ctx_w->error_callback; - delete ctx_w->success_callback; - ctx_w->result.Reset(); + if (ctx_w->error_callback != nullptr) { + CHECK_NAPI_RESULT(napi_delete_reference(ctx_w->env, ctx_w->error_callback)); + } + if (ctx_w->success_callback != nullptr) { + CHECK_NAPI_RESULT(napi_delete_reference(ctx_w->env, ctx_w->success_callback)); + } + if (ctx_w->result != nullptr) { + CHECK_NAPI_RESULT(napi_delete_reference(ctx_w->env, ctx_w->result)); + } free(ctx_w->include_path); free(ctx_w->linefeed); diff --git a/src/sass_context_wrapper.h b/src/sass_context_wrapper.h index 4aa35684a..a17a7cd7f 100644 --- a/src/sass_context_wrapper.h +++ b/src/sass_context_wrapper.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include "custom_function_bridge.h" @@ -37,17 +36,17 @@ extern "C" { uv_async_t async; uv_work_t request; - // v8 and nan related - Nan::Persistent result; - Nan::AsyncResource* async_resource; - Nan::Callback* error_callback; - Nan::Callback* success_callback; + // v8 and napi related + napi_env env; + napi_ref result; + napi_ref error_callback; + napi_ref success_callback; std::vector function_bridges; std::vector importer_bridges; }; - struct sass_context_wrapper* sass_make_context_wrapper(void); + struct sass_context_wrapper* sass_make_context_wrapper(napi_env); void sass_free_context_wrapper(struct sass_context_wrapper*); #ifdef __cplusplus diff --git a/src/sass_types/boolean.cpp b/src/sass_types/boolean.cpp index 2d4793238..a69865cf0 100644 --- a/src/sass_types/boolean.cpp +++ b/src/sass_types/boolean.cpp @@ -1,73 +1,112 @@ -#include #include "boolean.h" namespace SassTypes { - Nan::Persistent Boolean::constructor; + napi_ref Boolean::constructor = nullptr; bool Boolean::constructor_locked = false; - Boolean::Boolean(bool _value) { - value = sass_make_boolean(_value); - } + Boolean::Boolean(bool v) : value(v), js_object(nullptr) {} Boolean& Boolean::get_singleton(bool v) { static Boolean instance_false(false), instance_true(true); return v ? instance_true : instance_false; } - v8::Local Boolean::get_constructor() { - Nan::EscapableHandleScope scope; - v8::Local conslocal; - if (constructor.IsEmpty()) { - v8::Local tpl = Nan::New(New); + napi_value Boolean::construct_and_wrap_instance(napi_env env, napi_value ctor, Boolean* b) { + Napi::EscapableHandleScope scope(env); + + napi_value instance; + CHECK_NAPI_RESULT(napi_new_instance(env, ctor, 0, nullptr, &instance)); + CHECK_NAPI_RESULT(napi_wrap(env, instance, b, nullptr, nullptr, nullptr)); + CHECK_NAPI_RESULT(napi_create_reference(env, instance, 1, &(b->js_object))); + + return scope.Escape(instance); + } + + napi_value Boolean::get_constructor(napi_env env) { + Napi::EscapableHandleScope scope(env); + napi_value ctor; - tpl->SetClassName(Nan::New("SassBoolean").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - Nan::SetPrototypeTemplate(tpl, "getValue", Nan::New(GetValue)); + if (Boolean::constructor) { + CHECK_NAPI_RESULT(napi_get_reference_value(env, Boolean::constructor, &ctor)); + } else { + napi_property_descriptor methods[] = { + { "getValue", nullptr, Boolean::GetValue }, + }; - conslocal = Nan::GetFunction(tpl).ToLocalChecked(); - constructor.Reset(conslocal); + CHECK_NAPI_RESULT(napi_define_class(env, "SassBoolean", NAPI_AUTO_LENGTH, Boolean::New, nullptr, 1, methods, &ctor)); + CHECK_NAPI_RESULT(napi_create_reference(env, ctor, 1, &Boolean::constructor)); - get_singleton(false).js_object.Reset(Nan::NewInstance(conslocal).ToLocalChecked()); - Nan::SetInternalFieldPointer(Nan::New(get_singleton(false).js_object), 0, &get_singleton(false)); - Nan::Set(conslocal, Nan::New("FALSE").ToLocalChecked(), Nan::New(get_singleton(false).js_object)); + Boolean& falseSingleton = get_singleton(false); + napi_value instance = construct_and_wrap_instance(env, ctor, &falseSingleton); + CHECK_NAPI_RESULT(napi_set_named_property(env, ctor, "FALSE", instance)); - get_singleton(true).js_object.Reset(Nan::NewInstance(conslocal).ToLocalChecked()); - Nan::SetInternalFieldPointer(Nan::New(get_singleton(true).js_object), 0, &get_singleton(true)); - Nan::Set(conslocal, Nan::New("TRUE").ToLocalChecked(), Nan::New(get_singleton(true).js_object)); + Boolean& trueSingleton = get_singleton(true); + instance = construct_and_wrap_instance(env, ctor, &trueSingleton); + CHECK_NAPI_RESULT(napi_set_named_property(env, ctor, "TRUE", instance)); constructor_locked = true; - } else { - conslocal = Nan::New(constructor); } - return scope.Escape(conslocal); + return scope.Escape(ctor); } - v8::Local Boolean::get_js_object() { - return Nan::New(this->js_object); + Sass_Value* Boolean::get_sass_value() { + return sass_make_boolean(value); } - v8::Local Boolean::get_js_boolean() { - return sass_boolean_get_value(this->value) ? Nan::True() : Nan::False(); + napi_value Boolean::get_js_object(napi_env env) { + Napi::EscapableHandleScope scope(env); + napi_value v; + CHECK_NAPI_RESULT(napi_get_reference_value(env, this->js_object, &v)); + return scope.Escape(v); } - NAN_METHOD(Boolean::New) { - if (info.IsConstructCall()) { + napi_value Boolean::New(napi_env env, napi_callback_info info) { + napi_value t; + CHECK_NAPI_RESULT(napi_get_new_target(env, info, &t)); + bool r = (t != nullptr); + + if (r) { if (constructor_locked) { - return Nan::ThrowTypeError("Cannot instantiate SassBoolean"); + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Cannot instantiate SassBoolean")); + return nullptr; } - } - else { - if (info.Length() != 1 || !info[0]->IsBoolean()) { - return Nan::ThrowTypeError("Expected one boolean argument"); + } else { + size_t argc = 1; + napi_value argv[1]; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr)); + + if (argc != 1) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected one boolean argument")); + return nullptr; + } + + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, argv[0], &t)); + + if (t != napi_boolean) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected one boolean argument")); + return nullptr; } - info.GetReturnValue().Set(get_singleton(Nan::To(info[0]).FromJust()).get_js_object()); + CHECK_NAPI_RESULT(napi_get_value_bool(env, argv[0], &r)); + napi_value obj = Boolean::get_singleton(r).get_js_object(env); + return obj; } + return nullptr; } - NAN_METHOD(Boolean::GetValue) { - info.GetReturnValue().Set(Boolean::Unwrap(info.This())->get_js_boolean()); + napi_value Boolean::GetValue(napi_env env, napi_callback_info info) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + Boolean *out = static_cast(Factory::unwrap(env, _this)); + if (out) { + napi_value b; + CHECK_NAPI_RESULT(napi_get_boolean(env, out->value, &b)); + return b; + } + return nullptr; } } diff --git a/src/sass_types/boolean.h b/src/sass_types/boolean.h index 721a41c02..ed63f3a71 100644 --- a/src/sass_types/boolean.h +++ b/src/sass_types/boolean.h @@ -1,30 +1,33 @@ #ifndef SASS_TYPES_BOOLEAN_H #define SASS_TYPES_BOOLEAN_H -#include #include "value.h" #include "sass_value_wrapper.h" +#include namespace SassTypes { class Boolean : public SassTypes::Value { public: static Boolean& get_singleton(bool); - static v8::Local get_constructor(); + static napi_value get_constructor(napi_env env); - v8::Local get_js_object(); + Sass_Value* get_sass_value(); + napi_value get_js_object(napi_env env); - static NAN_METHOD(New); - static NAN_METHOD(GetValue); + static napi_value New(napi_env env, napi_callback_info info); + static napi_value GetValue(napi_env env, napi_callback_info info); private: Boolean(bool); - Nan::Persistent js_object; + static napi_value construct_and_wrap_instance(napi_env env, napi_value ctor, Boolean* b); - static Nan::Persistent constructor; + bool value; + napi_ref js_object; + + static napi_ref constructor; static bool constructor_locked; - v8::Local get_js_boolean(); }; } diff --git a/src/sass_types/color.cpp b/src/sass_types/color.cpp index 40358a2c3..38d37f97f 100644 --- a/src/sass_types/color.cpp +++ b/src/sass_types/color.cpp @@ -1,21 +1,22 @@ -#include #include "color.h" namespace SassTypes { - Color::Color(Sass_Value* v) : SassValueWrapper(v) {} + Color::Color(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* Color::construct(const std::vector> raw_val, Sass_Value **out) { + Sass_Value* Color::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { double a = 1.0, r = 0, g = 0, b = 0; - unsigned argb; + napi_valuetype t; + uint32_t argb; switch (raw_val.size()) { case 1: - if (!raw_val[0]->IsNumber()) { + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + if (t != napi_number) { return fail("Only argument should be an integer.", out); } - argb = Nan::To(raw_val[0]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_uint32(env, raw_val[0], &argb)); a = (double)((argb >> 030) & 0xff) / 0xff; r = (double)((argb >> 020) & 0xff); g = (double)((argb >> 010) & 0xff); @@ -23,21 +24,30 @@ namespace SassTypes break; case 4: - if (!raw_val[3]->IsNumber()) { + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[3], &t)); + if (t != napi_number) { return fail("Constructor arguments should be numbers exclusively.", out); } - - a = Nan::To(raw_val[3]).FromJust(); - NODE_SASS_FALLTHROUGH; + CHECK_NAPI_RESULT(napi_get_value_double(env, raw_val[3], &a)); + // fall through vvv case 3: - if (!raw_val[0]->IsNumber() || !raw_val[1]->IsNumber() || !raw_val[2]->IsNumber()) { + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + if (t != napi_number) { + return fail("Constructor arguments should be numbers exclusively.", out); + } + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[1], &t)); + if (t != napi_number) { + return fail("Constructor arguments should be numbers exclusively.", out); + } + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[2], &t)); + if (t != napi_number) { return fail("Constructor arguments should be numbers exclusively.", out); } - r = Nan::To(raw_val[0]).FromJust(); - g = Nan::To(raw_val[1]).FromJust(); - b = Nan::To(raw_val[2]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_double(env, raw_val[0], &r)); + CHECK_NAPI_RESULT(napi_get_value_double(env, raw_val[1], &g)); + CHECK_NAPI_RESULT(napi_get_value_double(env, raw_val[2], &b)); break; case 0: @@ -50,78 +60,52 @@ namespace SassTypes return *out = sass_make_color(r, g, b, a); } - void Color::initPrototype(v8::Local proto) { - Nan::SetPrototypeMethod(proto, "getR", GetR); - Nan::SetPrototypeMethod(proto, "getG", GetG); - Nan::SetPrototypeMethod(proto, "getB", GetB); - Nan::SetPrototypeMethod(proto, "getA", GetA); - Nan::SetPrototypeMethod(proto, "setR", SetR); - Nan::SetPrototypeMethod(proto, "setG", SetG); - Nan::SetPrototypeMethod(proto, "setB", SetB); - Nan::SetPrototypeMethod(proto, "setA", SetA); + napi_value Color::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + napi_property_descriptor descriptors[] = { + { "getR", nullptr, GetR }, + { "getG", nullptr, GetG }, + { "getB", nullptr, GetB }, + { "getA", nullptr, GetA }, + { "setR", nullptr, SetR }, + { "setG", nullptr, SetG }, + { "setB", nullptr, SetB }, + { "setA", nullptr, SetA }, + }; + + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 8, descriptors, &ctor)); + return ctor; } - NAN_METHOD(Color::GetR) { - info.GetReturnValue().Set(sass_color_get_r(Color::Unwrap(info.This())->value)); + napi_value Color::GetR(napi_env env, napi_callback_info info) { + return CommonGetNumber(env, info, sass_color_get_r); } - NAN_METHOD(Color::GetG) { - info.GetReturnValue().Set(sass_color_get_g(Color::Unwrap(info.This())->value)); + napi_value Color::GetG(napi_env env, napi_callback_info info) { + return CommonGetNumber(env, info, sass_color_get_g); } - NAN_METHOD(Color::GetB) { - info.GetReturnValue().Set(sass_color_get_b(Color::Unwrap(info.This())->value)); + napi_value Color::GetB(napi_env env, napi_callback_info info) { + return CommonGetNumber(env, info, sass_color_get_b); } - NAN_METHOD(Color::GetA) { - info.GetReturnValue().Set(sass_color_get_a(Color::Unwrap(info.This())->value)); + napi_value Color::GetA(napi_env env, napi_callback_info info) { + return CommonGetNumber(env, info, sass_color_get_a); } - NAN_METHOD(Color::SetR) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied value should be a number"); - } - - sass_color_set_r(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + napi_value Color::SetR(napi_env env, napi_callback_info info) { + return CommonSetNumber(env, info, sass_color_set_r); } - NAN_METHOD(Color::SetG) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied value should be a number"); - } - - sass_color_set_g(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + napi_value Color::SetG(napi_env env, napi_callback_info info) { + return CommonSetNumber(env, info, sass_color_set_g); } - NAN_METHOD(Color::SetB) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied value should be a number"); - } - - sass_color_set_b(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + napi_value Color::SetB(napi_env env, napi_callback_info info) { + return CommonSetNumber(env, info, sass_color_set_b); } - NAN_METHOD(Color::SetA) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied value should be a number"); - } - - sass_color_set_a(Color::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + napi_value Color::SetA(napi_env env, napi_callback_info info) { + return CommonSetNumber(env, info, sass_color_set_a); } } diff --git a/src/sass_types/color.h b/src/sass_types/color.h index 1bf904307..e0b9c6119 100644 --- a/src/sass_types/color.h +++ b/src/sass_types/color.h @@ -1,33 +1,25 @@ #ifndef SASS_TYPES_COLOR_H #define SASS_TYPES_COLOR_H -#include #include "sass_value_wrapper.h" -#if defined(__GNUC__) && __GNUC__ >= 7 -#define NODE_SASS_FALLTHROUGH __attribute__ ((fallthrough)) -#else -#define NODE_SASS_FALLTHROUGH -#endif - namespace SassTypes { class Color : public SassValueWrapper { public: - Color(Sass_Value*); + Color(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassColor"; } - static Sass_Value* construct(const std::vector>, Sass_Value **); - - static void initPrototype(v8::Local); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); - static NAN_METHOD(GetR); - static NAN_METHOD(GetG); - static NAN_METHOD(GetB); - static NAN_METHOD(GetA); - static NAN_METHOD(SetR); - static NAN_METHOD(SetG); - static NAN_METHOD(SetB); - static NAN_METHOD(SetA); + static napi_value GetR(napi_env env, napi_callback_info info); + static napi_value GetG(napi_env env, napi_callback_info info); + static napi_value GetB(napi_env env, napi_callback_info info); + static napi_value GetA(napi_env env, napi_callback_info info); + static napi_value SetR(napi_env env, napi_callback_info info); + static napi_value SetG(napi_env env, napi_callback_info info); + static napi_value SetB(napi_env env, napi_callback_info info); + static napi_value SetA(napi_env env, napi_callback_info info); }; } diff --git a/src/sass_types/error.cpp b/src/sass_types/error.cpp index 03c6307c4..f3026e2cc 100644 --- a/src/sass_types/error.cpp +++ b/src/sass_types/error.cpp @@ -1,24 +1,30 @@ -#include #include "error.h" #include "../create_string.h" namespace SassTypes { - Error::Error(Sass_Value* v) : SassValueWrapper(v) {} + Error::Error(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* Error::construct(const std::vector> raw_val, Sass_Value **out) { + Sass_Value* Error::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { char const* value = ""; if (raw_val.size() >= 1) { - if (!raw_val[0]->IsString()) { - return fail("Argument should be a string.", out); + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + + if (t != napi_string) { + return fail("First argument should be a string.", out); } - value = create_string(raw_val[0]); + value = create_string(env, raw_val[0]); } return *out = sass_make_error(value); } - void Error::initPrototype(v8::Local) {} + napi_value Error::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 0, nullptr, &ctor)); + return ctor; + } } diff --git a/src/sass_types/error.h b/src/sass_types/error.h index 01786fdaa..781b53cbb 100644 --- a/src/sass_types/error.h +++ b/src/sass_types/error.h @@ -1,18 +1,16 @@ #ifndef SASS_TYPES_ERROR_H #define SASS_TYPES_ERROR_H -#include #include "sass_value_wrapper.h" namespace SassTypes { class Error : public SassValueWrapper { public: - Error(Sass_Value*); + Error(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassError"; } - static Sass_Value* construct(const std::vector>, Sass_Value **); - - static void initPrototype(v8::Local); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); }; } diff --git a/src/sass_types/factory.cpp b/src/sass_types/factory.cpp index c650710c3..3ecf5e842 100644 --- a/src/sass_types/factory.cpp +++ b/src/sass_types/factory.cpp @@ -1,4 +1,3 @@ -#include #include "factory.h" #include "value.h" #include "number.h" @@ -9,64 +8,67 @@ #include "map.h" #include "null.h" #include "error.h" +#include "../common.h" namespace SassTypes { - SassTypes::Value* Factory::create(Sass_Value* v) { + SassTypes::Value* Factory::create(napi_env env, Sass_Value* v) { switch (sass_value_get_tag(v)) { case SASS_NUMBER: - return new Number(v); + return new Number(env, v); case SASS_STRING: - return new String(v); + return new String(env, v); case SASS_COLOR: - return new Color(v); + return new Color(env, v); case SASS_BOOLEAN: return &Boolean::get_singleton(sass_boolean_get_value(v)); case SASS_LIST: - return new List(v); + return new List(env, v); case SASS_MAP: - return new Map(v); + return new Map(env, v); case SASS_NULL: return &Null::get_singleton(); case SASS_ERROR: - return new Error(v); + return new Error(env, v); default: const char *msg = "Unknown type encountered."; - Nan::ThrowTypeError(msg); - return new Error(sass_make_error(msg)); + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, msg)); + return new Error(env, sass_make_error(msg)); } } - NAN_MODULE_INIT(Factory::initExports) { - Nan::HandleScope scope; - v8::Local types = Nan::New(); + void Factory::initExports(napi_env env, napi_value target) { + Napi::HandleScope scope(env); - Nan::Set(types, Nan::New("Number").ToLocalChecked(), Number::get_constructor()); - Nan::Set(types, Nan::New("String").ToLocalChecked(), String::get_constructor()); - Nan::Set(types, Nan::New("Color").ToLocalChecked(), Color::get_constructor()); - Nan::Set(types, Nan::New("Boolean").ToLocalChecked(), Boolean::get_constructor()); - Nan::Set(types, Nan::New("List").ToLocalChecked(), List::get_constructor()); - Nan::Set(types, Nan::New("Map").ToLocalChecked(), Map::get_constructor()); - Nan::Set(types, Nan::New("Null").ToLocalChecked(), Null::get_constructor()); - Nan::Set(types, Nan::New("Error").ToLocalChecked(), Error::get_constructor()); - Nan::Set(target, Nan::New("types").ToLocalChecked(), types); + napi_value types; + CHECK_NAPI_RESULT(napi_create_object(env, &types)); + + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Number", Number::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "String", String::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Color", Color::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Boolean", Boolean::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "List", List::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Map", Map::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Null", Null::get_constructor(env))); + CHECK_NAPI_RESULT(napi_set_named_property(env, types, "Error", Error::get_constructor(env))); + + CHECK_NAPI_RESULT(napi_set_named_property(env, target, "types", types)); } - Value* Factory::unwrap(v8::Local obj) { - if (obj->IsObject()) { - v8::Local v8_obj = obj.As(); - if (v8_obj->InternalFieldCount() == 1) { - return SassTypes::Value::Unwrap(v8_obj); - } - } - return NULL; + Value* Factory::unwrap(napi_env env, napi_value obj) { + void* wrapped; + napi_status status = napi_unwrap(env, obj, &wrapped); + if (status != napi_ok) { + wrapped = nullptr; + } + return static_cast(wrapped); } } diff --git a/src/sass_types/factory.h b/src/sass_types/factory.h index 27b7e3fa4..51ada9a3c 100644 --- a/src/sass_types/factory.h +++ b/src/sass_types/factory.h @@ -1,9 +1,9 @@ #ifndef SASS_TYPES_FACTORY_H #define SASS_TYPES_FACTORY_H -#include #include #include "value.h" +#include namespace SassTypes { @@ -11,9 +11,9 @@ namespace SassTypes // to wrap a given Sass_Value object. class Factory { public: - static NAN_MODULE_INIT(initExports); - static Value* create(Sass_Value*); - static Value* unwrap(v8::Local); + static void initExports(napi_env, napi_value); + static Value* create(napi_env, Sass_Value*); + static Value* unwrap(napi_env, napi_value); }; } diff --git a/src/sass_types/list.cpp b/src/sass_types/list.cpp index 4c946ec90..ad5df466c 100644 --- a/src/sass_types/list.cpp +++ b/src/sass_types/list.cpp @@ -1,101 +1,103 @@ -#include #include "list.h" namespace SassTypes { - List::List(Sass_Value* v) : SassValueWrapper(v) {} + List::List(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* List::construct(const std::vector> raw_val, Sass_Value **out) { - size_t length = 0; + Sass_Value* List::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { + uint32_t length = 0; bool comma = true; bool is_bracketed = false; if (raw_val.size() >= 1) { - if (!raw_val[0]->IsNumber()) { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + + if (t != napi_number) { return fail("First argument should be an integer.", out); } - length = Nan::To(raw_val[0]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_uint32(env, raw_val[0], &length)); if (raw_val.size() >= 2) { - if (!raw_val[1]->IsBoolean()) { + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[1], &t)); + + if (t != napi_boolean) { return fail("Second argument should be a boolean.", out); } - comma = Nan::To(raw_val[1]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_bool(env, raw_val[1], &comma)); } } return *out = sass_make_list(length, comma ? SASS_COMMA : SASS_SPACE, is_bracketed); } - void List::initPrototype(v8::Local proto) { - Nan::SetPrototypeMethod(proto, "getLength", GetLength); - Nan::SetPrototypeMethod(proto, "getSeparator", GetSeparator); - Nan::SetPrototypeMethod(proto, "setSeparator", SetSeparator); - Nan::SetPrototypeMethod(proto, "getValue", GetValue); - Nan::SetPrototypeMethod(proto, "setValue", SetValue); + napi_value List::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + napi_property_descriptor descriptors[] = { + { "getLength", nullptr, GetLength }, + { "getSeparator", nullptr, GetSeparator }, + { "setSeparator", nullptr, SetSeparator }, + { "getValue", nullptr, GetValue }, + { "setValue", nullptr, SetValue }, + }; + + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 5, descriptors, &ctor)); + return ctor; } - NAN_METHOD(List::GetValue) { - - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } - - Sass_Value* list = List::Unwrap(info.This())->value; - size_t index = Nan::To(info[0]).FromJust(); - - - if (index >= sass_list_get_length(list)) { - return Nan::ThrowRangeError(Nan::New("Out of bound index").ToLocalChecked()); - } + napi_value List::GetValue(napi_env env, napi_callback_info info) { + return CommonGetIndexedValue(env, info, sass_list_get_length, sass_list_get_value); + } - info.GetReturnValue().Set(Factory::create(sass_list_get_value(list, Nan::To(info[0]).FromJust()))->get_js_object()); + napi_value List::SetValue(napi_env env, napi_callback_info info) { + return CommonSetIndexedValue(env, info, sass_list_set_value); } - NAN_METHOD(List::SetValue) { - if (info.Length() != 2) { - return Nan::ThrowTypeError("Expected two arguments"); - } + napi_value List::GetSeparator(napi_env env, napi_callback_info info) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } + bool v = sass_list_get_separator(unwrap(env, _this)->value) == SASS_COMMA; + napi_value ret; + CHECK_NAPI_RESULT(napi_get_boolean(env, v, &ret)); + return ret; + } - if (!info[1]->IsObject()) { - return Nan::ThrowTypeError("Supplied value should be a SassValue object"); - } + napi_value List::SetSeparator(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value arg; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &arg, &_this, nullptr)); - Value* sass_value = Factory::unwrap(info[1]); - if (sass_value) { - sass_list_set_value(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); - } else { - Nan::ThrowTypeError("A SassValue is expected as the list item"); + if (argc != 1) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected just one argument")); + return nullptr; } - } - NAN_METHOD(List::GetSeparator) { - info.GetReturnValue().Set(sass_list_get_separator(List::Unwrap(info.This())->value) == SASS_COMMA); - } + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, arg, &t)); - NAN_METHOD(List::SetSeparator) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); + if (t != napi_boolean) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied value should be a boolean")); + return nullptr; } - if (!info[0]->IsBoolean()) { - return Nan::ThrowTypeError("Supplied value should be a boolean"); - } + bool b; + CHECK_NAPI_RESULT(napi_get_value_bool(env, arg, &b)); - sass_list_set_separator(List::Unwrap(info.This())->value, Nan::To(info[0]).FromJust() ? SASS_COMMA : SASS_SPACE); + sass_list_set_separator(unwrap(env, _this)->value, b ? SASS_COMMA : SASS_SPACE); + return nullptr; } - NAN_METHOD(List::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_list_get_length(List::Unwrap(info.This())->value))); + napi_value List::GetLength(napi_env env, napi_callback_info info) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + size_t s = sass_list_get_length(unwrap(env, _this)->value); + napi_value ret; + CHECK_NAPI_RESULT(napi_create_double(env, (double)s, &ret)); + return ret; } } diff --git a/src/sass_types/list.h b/src/sass_types/list.h index c43b75484..b7de690fb 100644 --- a/src/sass_types/list.h +++ b/src/sass_types/list.h @@ -1,24 +1,22 @@ #ifndef SASS_TYPES_LIST_H #define SASS_TYPES_LIST_H -#include #include "sass_value_wrapper.h" namespace SassTypes { class List : public SassValueWrapper { public: - List(Sass_Value*); + List(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassList"; } - static Sass_Value* construct(const std::vector>, Sass_Value **); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); - static void initPrototype(v8::Local); - - static NAN_METHOD(GetValue); - static NAN_METHOD(SetValue); - static NAN_METHOD(GetSeparator); - static NAN_METHOD(SetSeparator); - static NAN_METHOD(GetLength); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value GetSeparator(napi_env env, napi_callback_info info); + static napi_value SetSeparator(napi_env env, napi_callback_info info); + static napi_value GetLength(napi_env env, napi_callback_info info); }; } diff --git a/src/sass_types/map.cpp b/src/sass_types/map.cpp index ae4a260bf..65cb77bcb 100644 --- a/src/sass_types/map.cpp +++ b/src/sass_types/map.cpp @@ -1,118 +1,63 @@ -#include #include "map.h" namespace SassTypes { - Map::Map(Sass_Value* v) : SassValueWrapper(v) {} + Map::Map(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* Map::construct(const std::vector> raw_val, Sass_Value **out) { - size_t length = 0; + Sass_Value* Map::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { + uint32_t length = 0; if (raw_val.size() >= 1) { - if (!raw_val[0]->IsNumber()) { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + + if (t != napi_number) { return fail("First argument should be an integer.", out); } - length = Nan::To(raw_val[0]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_uint32(env, raw_val[0], &length)); } return *out = sass_make_map(length); } - void Map::initPrototype(v8::Local proto) { - Nan::SetPrototypeMethod(proto, "getLength", GetLength); - Nan::SetPrototypeMethod(proto, "getKey", GetKey); - Nan::SetPrototypeMethod(proto, "setKey", SetKey); - Nan::SetPrototypeMethod(proto, "getValue", GetValue); - Nan::SetPrototypeMethod(proto, "setValue", SetValue); + napi_value Map::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + napi_property_descriptor descriptors[] = { + { "getLength", nullptr, GetLength }, + { "getKey", nullptr, GetKey }, + { "setKey", nullptr, SetKey }, + { "getValue", nullptr, GetValue }, + { "setValue", nullptr, SetValue }, + }; + + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 5, descriptors, &ctor)); + return ctor; } - NAN_METHOD(Map::GetValue) { - - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } - - Sass_Value* map = Map::Unwrap(info.This())->value; - size_t index = Nan::To(info[0]).FromJust(); - - - if (index >= sass_map_get_length(map)) { - return Nan::ThrowRangeError(Nan::New("Out of bound index").ToLocalChecked()); - } - - info.GetReturnValue().Set(Factory::create(sass_map_get_value(map, Nan::To(info[0]).FromJust()))->get_js_object()); + napi_value Map::GetValue(napi_env env, napi_callback_info info) { + return CommonGetIndexedValue(env, info, sass_map_get_length, sass_map_get_value); } - NAN_METHOD(Map::SetValue) { - if (info.Length() != 2) { - return Nan::ThrowTypeError("Expected two arguments"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } - - if (!info[1]->IsObject()) { - return Nan::ThrowTypeError("Supplied value should be a SassValue object"); - } - - Value* sass_value = Factory::unwrap(info[1]); - if (sass_value) { - sass_map_set_value(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); - } else { - Nan::ThrowTypeError("A SassValue is expected as a map value"); - } + napi_value Map::SetValue(napi_env env, napi_callback_info info) { + return CommonSetIndexedValue(env, info, sass_map_set_value); } - NAN_METHOD(Map::GetKey) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } - - Sass_Value* map = Map::Unwrap(info.This())->value; - size_t index = Nan::To(info[0]).FromJust(); - - - if (index >= sass_map_get_length(map)) { - return Nan::ThrowRangeError(Nan::New("Out of bound index").ToLocalChecked()); - } - - SassTypes::Value* obj = Factory::create(sass_map_get_key(map, Nan::To(info[0]).FromJust())); - v8::Local js_obj = obj->get_js_object(); - info.GetReturnValue().Set(js_obj); + napi_value Map::GetKey(napi_env env, napi_callback_info info) { + return CommonGetIndexedValue(env, info, sass_map_get_length, sass_map_get_key); } - NAN_METHOD(Map::SetKey) { - if (info.Length() != 2) { - return Nan::ThrowTypeError("Expected two arguments"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied index should be an integer"); - } - - if (!info[1]->IsObject()) { - return Nan::ThrowTypeError("Supplied value should be a SassValue object"); - } - - Value* sass_value = Factory::unwrap(info[1]); - if (sass_value) { - sass_map_set_key(Map::Unwrap(info.This())->value, Nan::To(info[0]).FromJust(), sass_value->get_sass_value()); - } else { - Nan::ThrowTypeError("A SassValue is expected as a map key"); - } + napi_value Map::SetKey(napi_env env, napi_callback_info info) { + return CommonSetIndexedValue(env, info, sass_map_set_key); } - NAN_METHOD(Map::GetLength) { - info.GetReturnValue().Set(Nan::New(sass_map_get_length(Map::Unwrap(info.This())->value))); + napi_value Map::GetLength(napi_env env, napi_callback_info info) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + size_t s = sass_map_get_length(unwrap(env, _this)->value); + napi_value ret; + CHECK_NAPI_RESULT(napi_create_double(env, (double)s, &ret)); + return ret; } } diff --git a/src/sass_types/map.h b/src/sass_types/map.h index 832585dd5..54c65c647 100644 --- a/src/sass_types/map.h +++ b/src/sass_types/map.h @@ -1,24 +1,22 @@ #ifndef SASS_TYPES_MAP_H #define SASS_TYPES_MAP_H -#include #include "sass_value_wrapper.h" namespace SassTypes { class Map : public SassValueWrapper { public: - Map(Sass_Value*); + Map(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassMap"; } - static Sass_Value* construct(const std::vector>, Sass_Value **); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); - static void initPrototype(v8::Local); - - static NAN_METHOD(GetValue); - static NAN_METHOD(SetValue); - static NAN_METHOD(GetKey); - static NAN_METHOD(SetKey); - static NAN_METHOD(GetLength); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value GetKey(napi_env env, napi_callback_info info); + static napi_value SetKey(napi_env env, napi_callback_info info); + static napi_value GetLength(napi_env env, napi_callback_info info); }; } diff --git a/src/sass_types/null.cpp b/src/sass_types/null.cpp index 69f4c216d..bc5d544c1 100644 --- a/src/sass_types/null.cpp +++ b/src/sass_types/null.cpp @@ -1,57 +1,73 @@ -#include #include "null.h" namespace SassTypes { - Nan::Persistent Null::constructor; + napi_ref Null::constructor = nullptr; bool Null::constructor_locked = false; - Null::Null() { - value = sass_make_null(); - } + Null::Null() : js_object(nullptr) {} Null& Null::get_singleton() { static Null singleton_instance; return singleton_instance; } - v8::Local Null::get_constructor() { - Nan::EscapableHandleScope scope; - v8::Local conslocal; - if (constructor.IsEmpty()) { - v8::Local tpl = Nan::New(New); + napi_value Null::construct_and_wrap_instance(napi_env env, napi_value ctor, Null* n) { + Napi::EscapableHandleScope scope(env); - tpl->SetClassName(Nan::New("SassNull").ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); + napi_value instance; + CHECK_NAPI_RESULT(napi_new_instance(env, ctor, 0, nullptr, &instance)); + CHECK_NAPI_RESULT(napi_wrap(env, instance, n, nullptr, nullptr, nullptr)); + CHECK_NAPI_RESULT(napi_create_reference(env, instance, 1, &(n->js_object))); - conslocal = Nan::GetFunction(tpl).ToLocalChecked(); - constructor.Reset(conslocal); + return scope.Escape(instance); + } - get_singleton().js_object.Reset(Nan::NewInstance(conslocal).ToLocalChecked()); - Nan::SetInternalFieldPointer(Nan::New(get_singleton().js_object), 0, &get_singleton()); - Nan::Set(conslocal, Nan::New("NULL").ToLocalChecked(), Nan::New(get_singleton().js_object)); + napi_value Null::get_constructor(napi_env env) { + Napi::EscapableHandleScope scope(env); + napi_value ctor; - constructor_locked = true; + if (Null::constructor) { + CHECK_NAPI_RESULT(napi_get_reference_value(env, Null::constructor, &ctor)); } else { - conslocal = Nan::New(constructor); + CHECK_NAPI_RESULT(napi_define_class(env, "SassNull", NAPI_AUTO_LENGTH, Null::New, nullptr, 0, nullptr, &ctor)); + CHECK_NAPI_RESULT(napi_create_reference(env, ctor, 1, &Null::constructor)); + + Null& singleton = get_singleton(); + napi_value instance = construct_and_wrap_instance(env, ctor, &singleton); + + CHECK_NAPI_RESULT(napi_set_named_property(env, ctor, "NULL", instance)); + + constructor_locked = true; } - return scope.Escape(conslocal); + return scope.Escape(ctor); + } + + Sass_Value* Null::get_sass_value() { + return sass_make_null(); } - v8::Local Null::get_js_object() { - return Nan::New(this->js_object); + napi_value Null::get_js_object(napi_env env) { + napi_value v; + CHECK_NAPI_RESULT(napi_get_reference_value(env, this->js_object, &v)); + return v; } - NAN_METHOD(Null::New) { + napi_value Null::New(napi_env env, napi_callback_info info) { + napi_value t; + CHECK_NAPI_RESULT(napi_get_new_target(env, info, &t)); + bool r = (t != nullptr); - if (info.IsConstructCall()) { + if (r) { if (constructor_locked) { - return Nan::ThrowTypeError("Cannot instantiate SassNull"); + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Cannot instantiate SassNull")); } } else { - info.GetReturnValue().Set(get_singleton().get_js_object()); + napi_value obj = Null::get_singleton().get_js_object(env); + return obj; } + return nullptr; } } diff --git a/src/sass_types/null.h b/src/sass_types/null.h index 15b64ba09..b01132fd4 100644 --- a/src/sass_types/null.h +++ b/src/sass_types/null.h @@ -1,27 +1,33 @@ #ifndef SASS_TYPES_NULL_H #define SASS_TYPES_NULL_H -#include #include "value.h" +// node-sass only builds with MSVC 2013 which doesn't appear to have char16_t defined +#define char16_t wchar_t + +#include + namespace SassTypes { class Null : public SassTypes::Value { public: static Null& get_singleton(); - static v8::Local get_constructor(); + static napi_value get_constructor(napi_env env); Sass_Value* get_sass_value(); - v8::Local get_js_object(); + napi_value get_js_object(napi_env env); - static NAN_METHOD(New); + static napi_value New(napi_env env, napi_callback_info info); private: Null(); - Nan::Persistent js_object; + static napi_value construct_and_wrap_instance(napi_env env, napi_value ctor, Null* n); + + napi_ref js_object; - static Nan::Persistent constructor; + static napi_ref constructor; static bool constructor_locked; }; } diff --git a/src/sass_types/number.cpp b/src/sass_types/number.cpp index d8d303ee0..56de8fea9 100644 --- a/src/sass_types/number.cpp +++ b/src/sass_types/number.cpp @@ -1,75 +1,64 @@ -#include #include "number.h" #include "../create_string.h" namespace SassTypes { - Number::Number(Sass_Value* v) : SassValueWrapper(v) {} + Number::Number(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* Number::construct(const std::vector> raw_val, Sass_Value **out) { + Sass_Value* Number::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { double value = 0; char const* unit = ""; if (raw_val.size() >= 1) { - if (!raw_val[0]->IsNumber()) { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + + if (t != napi_number) { return fail("First argument should be a number.", out); } - value = Nan::To(raw_val[0]).FromJust(); + CHECK_NAPI_RESULT(napi_get_value_double(env, raw_val[0], &value)); if (raw_val.size() >= 2) { - if (!raw_val[1]->IsString()) { + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[1], &t)); + + if (t != napi_string) { return fail("Second argument should be a string.", out); } - unit = create_string(raw_val[1]); - *out = sass_make_number(value, unit); - delete unit; - return *out; - + unit = create_string(env, raw_val[1]); } } return *out = sass_make_number(value, unit); } - void Number::initPrototype(v8::Local proto) { - Nan::SetPrototypeMethod(proto, "getValue", GetValue); - Nan::SetPrototypeMethod(proto, "getUnit", GetUnit); - Nan::SetPrototypeMethod(proto, "setValue", SetValue); - Nan::SetPrototypeMethod(proto, "setUnit", SetUnit); + napi_value Number::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + napi_property_descriptor descriptors [] = { + { "getValue", nullptr, GetValue }, + { "getUnit", nullptr, GetUnit }, + { "setValue", nullptr, SetValue }, + { "setUnit", nullptr, SetUnit }, + }; + + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 4, descriptors, &ctor)); + return ctor; } - NAN_METHOD(Number::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_number_get_value(Number::Unwrap(info.This())->value))); + napi_value Number::GetValue(napi_env env, napi_callback_info info) { + return CommonGetNumber(env, info, sass_number_get_value); } - NAN_METHOD(Number::GetUnit) { - info.GetReturnValue().Set(Nan::New(sass_number_get_unit(Number::Unwrap(info.This())->value)).ToLocalChecked()); + napi_value Number::GetUnit(napi_env env, napi_callback_info info) { + return CommonGetString(env, info, sass_number_get_unit); } - NAN_METHOD(Number::SetValue) { - - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsNumber()) { - return Nan::ThrowTypeError("Supplied value should be a number"); - } - - sass_number_set_value(Number::Unwrap(info.This())->value, Nan::To(info[0]).FromJust()); + napi_value Number::SetValue(napi_env env, napi_callback_info info) { + return CommonSetNumber(env, info, sass_number_set_value); } - NAN_METHOD(Number::SetUnit) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("Supplied value should be a string"); - } - - sass_number_set_unit(Number::Unwrap(info.This())->value, create_string(info[0])); + napi_value Number::SetUnit(napi_env env, napi_callback_info info) { + return CommonSetString(env, info, sass_number_set_unit); } } diff --git a/src/sass_types/number.h b/src/sass_types/number.h index 48a02361f..7c99c9dd1 100644 --- a/src/sass_types/number.h +++ b/src/sass_types/number.h @@ -1,24 +1,21 @@ #ifndef SASS_TYPES_NUMBER_H #define SASS_TYPES_NUMBER_H -#include #include "sass_value_wrapper.h" namespace SassTypes { - class Number : public SassValueWrapper { public: - Number(Sass_Value*); + Number(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassNumber"; } - static Sass_Value* construct(const std::vector>, Sass_Value **out); - - static void initPrototype(v8::Local); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); - static NAN_METHOD(GetValue); - static NAN_METHOD(GetUnit); - static NAN_METHOD(SetValue); - static NAN_METHOD(SetUnit); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value GetUnit(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value SetUnit(napi_env env, napi_callback_info info); }; } diff --git a/src/sass_types/sass_value_wrapper.h b/src/sass_types/sass_value_wrapper.h index 52a351187..df5c3515b 100644 --- a/src/sass_types/sass_value_wrapper.h +++ b/src/sass_types/sass_value_wrapper.h @@ -3,99 +3,303 @@ #include #include -#include #include "value.h" #include "factory.h" +#include "../create_string.h" +#include namespace SassTypes { // Include this in any SassTypes::Value subclasses to handle all the heavy lifting of constructing JS // objects and wrapping sass values inside them template - /* class SassValueWrapper : public SassTypes::Value { */ - class SassValueWrapper : public SassTypes::Value { - public: - static char const* get_constructor_name() { return "SassValue"; } + class SassValueWrapper : public SassTypes::Value { + public: + static char const* get_constructor_name() { return "SassValue"; } - SassValueWrapper(Sass_Value* v) : Value(v) { } - v8::Local get_js_object(); + SassValueWrapper(napi_env, Sass_Value*); + virtual ~SassValueWrapper(); - static v8::Local get_constructor(); - static v8::Local get_constructor_template(); - static NAN_METHOD(New); - static Sass_Value *fail(const char *, Sass_Value **); + Sass_Value* get_sass_value(); + napi_value get_js_object(napi_env); - /* private: */ - static Nan::Persistent constructor; - }; + static napi_value get_constructor(napi_env); + static napi_value New(napi_env env, napi_callback_info info); + static Sass_Value *fail(const char *, Sass_Value **); + + protected: + Sass_Value* value; + static T* unwrap(napi_env, napi_value); + + static napi_value CommonGetNumber(napi_env env, napi_callback_info info, double(fnc)(const Sass_Value*)); + static napi_value CommonSetNumber(napi_env env, napi_callback_info info, void(fnc)(Sass_Value*, double)); + + static napi_value CommonGetString(napi_env env, napi_callback_info info, const char*(fnc)(const Sass_Value*)); + static napi_value CommonSetString(napi_env env, napi_callback_info info, void(fnc)(Sass_Value*, char*)); + + static napi_value CommonGetIndexedValue(napi_env env, napi_callback_info info, size_t(lenfnc)(const Sass_Value*), Sass_Value*(getfnc)(const Sass_Value*, size_t)); + static napi_value CommonSetIndexedValue(napi_env env, napi_callback_info info, void(setfnc)(Sass_Value*, size_t, Sass_Value*)); + + private: + static napi_ref constructor; + napi_ref js_object; + napi_env e; + }; template - Nan::Persistent SassValueWrapper::constructor; + napi_ref SassValueWrapper::constructor = nullptr; template - v8::Local SassValueWrapper::get_js_object() { - if (this->persistent().IsEmpty()) { - v8::Local wrapper = Nan::NewInstance(T::get_constructor()).ToLocalChecked(); - this->Wrap(wrapper); - } + SassValueWrapper::SassValueWrapper(napi_env env, Sass_Value* v) { + this->value = sass_clone_value(v); + this->e = env; + this->js_object = nullptr; + } + + template + SassValueWrapper::~SassValueWrapper() { + CHECK_NAPI_RESULT(napi_delete_reference(this->e, this->js_object)); + sass_delete_value(this->value); + } + + template + napi_value SassValueWrapper::CommonGetNumber(napi_env env, napi_callback_info info, double(fnc)(const Sass_Value*)) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + double d = fnc(unwrap(env, _this)->value); + + napi_value ret; + CHECK_NAPI_RESULT(napi_create_double(env, d, &ret)); + return ret; + } + + template + napi_value SassValueWrapper::CommonSetNumber(napi_env env, napi_callback_info info, void(fnc)(Sass_Value*, double)) { + size_t argc = 1; + napi_value arg; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &arg, &_this, nullptr)); - return this->handle(); + if (argc != 1) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected just one argument")); + return nullptr; } + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, arg, &t)); + + if (t != napi_number) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied value should be a number")); + return nullptr; + } + + double d; + CHECK_NAPI_RESULT(napi_get_value_double(env, arg, &d)); + + fnc(unwrap(env, _this)->value, d); + return nullptr; + } + + template + napi_value SassValueWrapper::CommonGetString(napi_env env, napi_callback_info info, const char*(fnc)(const Sass_Value*)) { + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + + const char* v = fnc(unwrap(env, _this)->value); + int len = (int)strlen(v); + + napi_value str; + CHECK_NAPI_RESULT(napi_create_string_utf8(env, v, len, &str)); + return str; + } + template - v8::Local SassValueWrapper::get_constructor_template() { - Nan::EscapableHandleScope scope; - v8::Local tpl = Nan::New(New); - tpl->SetClassName(Nan::New(T::get_constructor_name()).ToLocalChecked()); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - T::initPrototype(tpl); + napi_value SassValueWrapper::CommonSetString(napi_env env, napi_callback_info info, void(fnc)(Sass_Value*, char*)) { + size_t argc = 1; + napi_value arg; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &arg, &_this, nullptr)); - return scope.Escape(tpl); + if (argc != 1) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected just one argument")); + return nullptr; } + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, arg, &t)); + + if (t != napi_string) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied value should be a string")); + return nullptr; + } + + char* s = create_string(env, arg); + + fnc(unwrap(env, _this)->value, s); + return nullptr; + } + template - v8::Local SassValueWrapper::get_constructor() { - if (constructor.IsEmpty()) { - constructor.Reset(Nan::GetFunction(T::get_constructor_template()).ToLocalChecked()); - } + napi_value SassValueWrapper::CommonGetIndexedValue(napi_env env, napi_callback_info info, size_t(lenfnc)(const Sass_Value*), Sass_Value*(getfnc)(const Sass_Value*,size_t)) { + size_t argc = 1; + napi_value arg; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, &arg, &_this, nullptr)); + + if (argc != 1) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected just one argument")); + return nullptr; + } + + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, arg, &t)); + + if (t != napi_number) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied index should be an integer")); + return nullptr; + } + + uint32_t index; + CHECK_NAPI_RESULT(napi_get_value_uint32(env, arg, &index)); + + Sass_Value* collection = unwrap(env, _this)->value; - return Nan::New(constructor); + if (index >= lenfnc(collection)) { + CHECK_NAPI_RESULT(napi_throw_range_error(env, nullptr, "Out of bound index")); + return nullptr; } + napi_value ret = Factory::create(env, getfnc(collection, index))->get_js_object(env); + return ret; + } + template - NAN_METHOD(SassValueWrapper::New) { - std::vector> localArgs(info.Length()); + napi_value SassValueWrapper::CommonSetIndexedValue(napi_env env, napi_callback_info info, void(setfnc)(Sass_Value*, size_t, Sass_Value*)) { + size_t argc = 2; + napi_value argv[2]; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, argv, &_this, nullptr)); - for (auto i = 0; i < info.Length(); ++i) { - localArgs[i] = info[i]; - } - if (info.IsConstructCall()) { - Sass_Value* value; - if (T::construct(localArgs, &value) != NULL) { - T* obj = new T(value); - sass_delete_value(value); - - obj->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - return Nan::ThrowError(Nan::New(sass_error_get_message(value)).ToLocalChecked()); - } + if (argc != 2) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Expected two arguments")); + return nullptr; + } + + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, argv[0], &t)); + + if (t != napi_number) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied index should be an integer")); + return nullptr; + } + + CHECK_NAPI_RESULT(napi_typeof(env, argv[1], &t)); + + if (t != napi_object) { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "Supplied value should be a SassValue object")); + return nullptr; + } + + uint32_t v; + CHECK_NAPI_RESULT(napi_get_value_uint32(env, argv[0], &v)); + + Value* sass_value = Factory::unwrap(env, argv[1]); + if (sass_value) { + setfnc(unwrap(env, _this)->value, v, sass_value->get_sass_value()); + } + else { + CHECK_NAPI_RESULT(napi_throw_type_error(env, nullptr, "A SassValue is expected")); + } + return nullptr; + } + + template + Sass_Value* SassValueWrapper::get_sass_value() { + return sass_clone_value(this->value); + } + + template + napi_value SassValueWrapper::get_js_object(napi_env env) { + if (this->js_object == nullptr) { + napi_value wrapper; + napi_value ctor = T::get_constructor(env); + CHECK_NAPI_RESULT(napi_new_instance(env, ctor, 0, nullptr, &wrapper)); + void* wrapped; + CHECK_NAPI_RESULT(napi_remove_wrap(env, wrapper, &wrapped)); + delete static_cast(wrapped); + CHECK_NAPI_RESULT(napi_wrap(env, wrapper, this, nullptr, nullptr, nullptr)); + CHECK_NAPI_RESULT(napi_create_reference(env, wrapper, 1, &this->js_object)); + } + + napi_value v; + CHECK_NAPI_RESULT(napi_get_reference_value(env, this->js_object, &v)); + return v; + } + + template + napi_value SassValueWrapper::get_constructor(napi_env env) { + Napi::EscapableHandleScope scope(env); + + napi_value ctor; + if (!constructor) { + ctor = T::getConstructor(env, New); + CHECK_NAPI_RESULT(napi_create_reference(env, ctor, 1, &constructor)); + } + else { + CHECK_NAPI_RESULT(napi_get_reference_value(env, constructor, &ctor)); + } + + return scope.Escape(ctor); + } + + template + napi_value SassValueWrapper::New(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value _this; + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, nullptr, &_this, nullptr)); + std::vector argv(argc); + CHECK_NAPI_RESULT(napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr)); + + napi_value t; + CHECK_NAPI_RESULT(napi_get_new_target(env, info, &t)); + bool r = (t != nullptr); + + if (r) { + Sass_Value* value; + if (T::construct(env, argv, &value) != NULL) { + T* obj = new T(env, value); + sass_delete_value(value); + + CHECK_NAPI_RESULT(napi_wrap(env, _this, obj, nullptr, nullptr, nullptr)); + CHECK_NAPI_RESULT(napi_create_reference(env, _this, 1, &obj->js_object)); + return _this; } else { - v8::Local cons = T::get_constructor(); - v8::Local inst; - if (Nan::NewInstance(cons, info.Length(), &localArgs[0]).ToLocal(&inst)) { - info.GetReturnValue().Set(inst); - } else { - info.GetReturnValue().Set(Nan::Undefined()); - } + CHECK_NAPI_RESULT(napi_throw_error(env, nullptr, sass_error_get_message(value))); + } + } else { + napi_value ctor = T::get_constructor(env); + napi_value instance; + napi_status status = napi_new_instance(env, ctor, argc, argv.data(), &instance); + if (status == napi_ok) { + return instance; } } + return nullptr; + } template - Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { - *out = sass_make_error(reason); - return NULL; - } + T* SassValueWrapper::unwrap(napi_env env, napi_value obj) { + /* This maybe NULL */ + return static_cast(Factory::unwrap(env, obj)); + } + + template + Sass_Value *SassValueWrapper::fail(const char *reason, Sass_Value **out) { + *out = sass_make_error(reason); + return NULL; + } } + #endif diff --git a/src/sass_types/string.cpp b/src/sass_types/string.cpp index c6f2c48fc..fd59fb802 100644 --- a/src/sass_types/string.cpp +++ b/src/sass_types/string.cpp @@ -1,48 +1,43 @@ -#include #include "string.h" #include "../create_string.h" namespace SassTypes { - String::String(Sass_Value* v) : SassValueWrapper(v) {} + String::String(napi_env env, Sass_Value* v) : SassValueWrapper(env, v) {} - Sass_Value* String::construct(const std::vector> raw_val, Sass_Value **out) { + Sass_Value* String::construct(napi_env env, const std::vector raw_val, Sass_Value **out) { char const* value = ""; if (raw_val.size() >= 1) { - if (!raw_val[0]->IsString()) { + napi_valuetype t; + CHECK_NAPI_RESULT(napi_typeof(env, raw_val[0], &t)); + + if (t != napi_string) { return fail("Argument should be a string.", out); } - value = create_string(raw_val[0]); - *out = sass_make_string(value); - delete value; - return *out; - - } else { - return *out = sass_make_string(value); + value = create_string(env, raw_val[0]); } + return *out = sass_make_string(value); } - void String::initPrototype(v8::Local proto) { - Nan::SetPrototypeMethod(proto, "getValue", GetValue); - Nan::SetPrototypeMethod(proto, "setValue", SetValue); - } + napi_value String::getConstructor(napi_env env, napi_callback cb) { + napi_value ctor; + napi_property_descriptor descriptors[] = { + { "getValue", nullptr, GetValue }, + { "setValue", nullptr, SetValue }, + }; - NAN_METHOD(String::GetValue) { - info.GetReturnValue().Set(Nan::New(sass_string_get_value(String::Unwrap(info.This())->value)).ToLocalChecked()); + CHECK_NAPI_RESULT(napi_define_class(env, get_constructor_name(), NAPI_AUTO_LENGTH, cb, nullptr, 2, descriptors, &ctor)); + return ctor; } - NAN_METHOD(String::SetValue) { - if (info.Length() != 1) { - return Nan::ThrowTypeError("Expected just one argument"); - } - - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("Supplied value should be a string"); - } + napi_value String::GetValue(napi_env env, napi_callback_info info) { + return CommonGetString(env, info, sass_string_get_value); + } - sass_string_set_value(String::Unwrap(info.This())->value, create_string(info[0])); + napi_value String::SetValue(napi_env env, napi_callback_info info) { + return CommonSetString(env, info, sass_string_set_value); } } diff --git a/src/sass_types/string.h b/src/sass_types/string.h index 2e72c8251..8152cbffb 100644 --- a/src/sass_types/string.h +++ b/src/sass_types/string.h @@ -1,21 +1,20 @@ #ifndef SASS_TYPES_STRING_H #define SASS_TYPES_STRING_H -#include #include "sass_value_wrapper.h" namespace SassTypes { class String : public SassValueWrapper { public: - String(Sass_Value*); + String(napi_env, Sass_Value*); static char const* get_constructor_name() { return "SassString"; } - static Sass_Value* construct(const std::vector>, Sass_Value **); - static void initPrototype(v8::Local); + static Sass_Value* construct(napi_env, const std::vector, Sass_Value **); + static napi_value getConstructor(napi_env, napi_callback); - static NAN_METHOD(GetValue); - static NAN_METHOD(SetValue); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); }; } diff --git a/src/sass_types/value.h b/src/sass_types/value.h index fa4703cb3..ec1a977ac 100644 --- a/src/sass_types/value.h +++ b/src/sass_types/value.h @@ -1,41 +1,17 @@ #ifndef SASS_TYPES_VALUE_H #define SASS_TYPES_VALUE_H -#include #include +#include "../common.h" +#include namespace SassTypes { // This is the interface that all sass values must comply with - class Value : public Nan::ObjectWrap { - + class Value { public: - virtual v8::Local get_js_object() =0; - - Value() { - - } - - Sass_Value* get_sass_value() { - return sass_clone_value(this->value); - } - - protected: - - Sass_Value* value; - - Value(Sass_Value* v) { - this->value = sass_clone_value(v); - } - - ~Value() { - sass_delete_value(this->value); - } - - static Sass_Value* fail(const char *reason, Sass_Value **out) { - *out = sass_make_error(reason); - return NULL; - } + virtual Sass_Value* get_sass_value() =0; + virtual napi_value get_js_object(napi_env env) = 0; }; } diff --git a/test/api.js b/test/api.js index 5db31137d..0660feadd 100644 --- a/test/api.js +++ b/test/api.js @@ -5,8 +5,8 @@ var assert = require('assert'), path = require('path'), read = fs.readFileSync, sassPath = process.env.NODESASS_COV - ? require.resolve('../lib-cov') - : require.resolve('../lib'), + ? require.resolve('../lib-cov') + : require.resolve('../lib'), sass = require(sassPath), fixture = path.join.bind(null, __dirname, 'fixtures'), resolveFixture = path.resolve.bind(null, __dirname, 'fixtures'); @@ -68,7 +68,7 @@ describe('api', function() { file: fixture('simple/index.scss'), sourceMap: false }, function(error, result) { - assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + assert.strictEqual(Object.prototype.hasOwnProperty.call(result, 'map'), false, 'result has a map property'); done(); }); }); @@ -78,7 +78,7 @@ describe('api', function() { file: fixture('simple/index.scss'), sourceMap: true }, function(error, result) { - assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + assert.strictEqual(Object.prototype.hasOwnProperty.call(result, 'map'), false, 'result has a map property'); done(); }); }); @@ -654,7 +654,7 @@ describe('api', function() { done(new Error('doesn\'t exist!')); } }, function(error) { - assert(/doesn\'t exist!/.test(error.message)); + assert(/doesn't exist!/.test(error.message)); done(); }); }); @@ -666,7 +666,7 @@ describe('api', function() { return new Error('doesn\'t exist!'); } }, function(error) { - assert(/doesn\'t exist!/.test(error.message)); + assert(/doesn't exist!/.test(error.message)); done(); }); }); @@ -1132,7 +1132,7 @@ describe('api', function() { } } }, function(error) { - assert.ok(/A SassValue is expected as the list item/.test(error.message)); + assert.ok(/A SassValue is expected/.test(error.message)); done(); }); }); @@ -1149,7 +1149,7 @@ describe('api', function() { } } }, function(error) { - assert.ok(/A SassValue is expected as a map key/.test(error.message)); + assert.ok(/A SassValue is expected/.test(error.message)); done(); }); }); @@ -1166,7 +1166,7 @@ describe('api', function() { } } }, function(error) { - assert.ok(/A SassValue is expected as a map value/.test(error.message)); + assert.ok(/A SassValue is expected/.test(error.message)); done(); }); }); @@ -1196,7 +1196,7 @@ describe('api', function() { }, bar: function(a) { assert.strictEqual(a, sass.NULL, - 'Supplied value should be the same instance as sass.NULL'); + 'Supplied value should be the same instance as sass.NULL'); assert.throws(function() { return new sass.types.Null(); @@ -1374,7 +1374,7 @@ describe('api', function() { sourceMap: false }); - assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + assert.strictEqual(Object.prototype.hasOwnProperty.call(result, 'map'), false, 'result has a map property'); done(); }); @@ -1384,7 +1384,7 @@ describe('api', function() { sourceMap: true }); - assert.strictEqual(result.hasOwnProperty('map'), false, 'result has a map property'); + assert.strictEqual(Object.prototype.hasOwnProperty.call(result, 'map'), false, 'result has a map property'); done(); }); @@ -1765,7 +1765,7 @@ describe('api', function() { return new Error('doesn\'t exist!'); } }); - }, /doesn\'t exist!/); + }, /doesn't exist!/); done(); }); diff --git a/test/binding.js b/test/binding.js index 200c7802b..c5d12024e 100644 --- a/test/binding.js +++ b/test/binding.js @@ -4,8 +4,8 @@ var assert = require('assert'), path = require('path'), etx = require('../lib/extensions'), binding = process.env.NODESASS_COV - ? require('../lib-cov/binding') - : require('../lib/binding'); + ? require('../lib-cov/binding') + : require('../lib/binding'); describe('binding', function() { describe('missing error', function() { @@ -100,14 +100,16 @@ describe('binding', function() { describe('with an unsupported runtime', function() { beforeEach(function() { - Object.defineProperty(process.versions, 'modules', { + Object.defineProperty(process.versions, 'napi', { value: 'baz', + configurable: true, }); }); afterEach(function() { - Object.defineProperty(process.versions, 'modules', { - value: 51, + Object.defineProperty(process.versions, 'napi', { + value: 3, + configurable: true, }); }); diff --git a/test/cli.js b/test/cli.js index 78a80910c..1830c3c5c 100644 --- a/test/cli.js +++ b/test/cli.js @@ -9,6 +9,10 @@ var assert = require('assert'), cli = path.join(__dirname, '..', 'bin', 'node-sass'), fixture = path.join.bind(null, __dirname, 'fixtures'); +function isNapiError(error) { + return /N-API is an experimental/.exec(error.toString()); +} + describe('cli', function() { // For some reason we experience random timeout failures in CI // due to spawn hanging/failing silently. See #1692. @@ -122,8 +126,6 @@ describe('cli', function() { describe('node-sass in.scss', function() { it('should compile a scss file', function(done) { - process.chdir(fixture('simple')); - var src = fixture('simple/index.scss'); var dest = fixture('simple/index.css'); var bin = spawn(cli, [src, dest]); @@ -131,14 +133,11 @@ describe('cli', function() { bin.once('close', function() { assert(fs.existsSync(dest)); fs.unlinkSync(dest); - process.chdir(__dirname); done(); }); }); it('should compile a scss file to custom destination', function(done) { - process.chdir(fixture('simple')); - var src = fixture('simple/index.scss'); var dest = fixture('simple/index-custom.css'); var bin = spawn(cli, [src, dest]); @@ -146,7 +145,6 @@ describe('cli', function() { bin.once('close', function() { assert(fs.existsSync(dest)); fs.unlinkSync(dest); - process.chdir(__dirname); done(); }); }); @@ -169,28 +167,25 @@ describe('cli', function() { }); it('should compile silently using the --quiet option', function(done) { - process.chdir(fixture('simple')); - var src = fixture('simple/index.scss'); var dest = fixture('simple/index.css'); var bin = spawn(cli, [src, dest, '--quiet']); var didEmit = false; - bin.stderr.once('data', function() { - didEmit = true; + bin.stderr.on('data', function(code) { + if (!isNapiError(code)) { + didEmit = true; + } }); bin.once('close', function() { assert.equal(didEmit, false); fs.unlinkSync(dest); - process.chdir(__dirname); done(); }); }); it('should still report errors with the --quiet option', function(done) { - process.chdir(fixture('invalid')); - var src = fixture('invalid/index.scss'); var dest = fixture('invalid/index.css'); var bin = spawn(cli, [src, dest, '--quiet']); @@ -202,7 +197,6 @@ describe('cli', function() { bin.once('close', function() { assert.equal(didEmit, true); - process.chdir(__dirname); done(); }); }); @@ -462,7 +456,8 @@ describe('cli', function() { var src = fixture('source-map-embed/index.scss'); var expectedCss = read(fixture('source-map-embed/expected.css'), 'utf8').trim().replace(/\r\n/g, '\n'); var result = ''; - var bin = spawn(cli, [ + var bin = require('child_process').spawn('node', [ + cli, src, '--source-map-embed', '--source-map', 'true' @@ -644,7 +639,8 @@ describe('cli', function() { describe('importer', function() { var dest = fixture('include-files/index.css'); var src = fixture('include-files/index.scss'); - var expected = read(fixture('include-files/expected-importer.css'), 'utf8').trim().replace(/\r\n/g, '\n'); + var expectedData = read(fixture('include-files/expected-data-importer.css'), 'utf8').trim().replace(/\r\n/g, '\n'); + var expectedFile = read(fixture('include-files/expected-file-importer.css'), 'utf8').trim().replace(/\r\n/g, '\n'); it('should override imports and fire callback with file and contents', function(done) { var bin = spawn(cli, [ @@ -653,7 +649,7 @@ describe('cli', function() { ]); bin.once('close', function() { - assert.equal(read(dest, 'utf8').trim(), expected); + assert.equal(read(dest, 'utf8').trim(), expectedData); fs.unlinkSync(dest); done(); }); @@ -667,7 +663,7 @@ describe('cli', function() { bin.once('close', function() { if (fs.existsSync(dest)) { - assert.equal(read(dest, 'utf8').trim(), ''); + assert.equal(read(dest, 'utf8').trim(), expectedFile); fs.unlinkSync(dest); } @@ -682,7 +678,7 @@ describe('cli', function() { ]); bin.once('close', function() { - assert.equal(read(dest, 'utf8').trim(), expected); + assert.equal(read(dest, 'utf8').trim(), expectedData); fs.unlinkSync(dest); done(); }); @@ -695,7 +691,7 @@ describe('cli', function() { ]); bin.once('close', function() { - assert.equal(read(dest, 'utf8').trim(), expected); + assert.equal(read(dest, 'utf8').trim(), expectedData); fs.unlinkSync(dest); done(); }); @@ -709,7 +705,7 @@ describe('cli', function() { bin.once('close', function() { if (fs.existsSync(dest)) { - assert.equal(read(dest, 'utf8').trim(), ''); + assert.equal(read(dest, 'utf8').trim(), expectedFile); fs.unlinkSync(dest); } @@ -724,7 +720,7 @@ describe('cli', function() { ]); bin.once('close', function() { - assert.equal(read(dest, 'utf8').trim(), expected); + assert.equal(read(dest, 'utf8').trim(), expectedData); fs.unlinkSync(dest); done(); }); @@ -737,7 +733,7 @@ describe('cli', function() { ]); bin.once('close', function() { - assert.equal(read(dest, 'utf8').trim(), expected); + assert.equal(read(dest, 'utf8').trim(), expectedData); fs.unlinkSync(dest); done(); }); @@ -761,9 +757,11 @@ describe('cli', function() { '--importer', fixture('extras/my_custom_importer_error.js') ]); - bin.stderr.once('data', function(code) { - assert.equal(JSON.parse(code).message, 'doesn\'t exist!'); - done(); + bin.stderr.on('data', function(code) { + if (!isNapiError(code)) { + assert.equal(JSON.parse(code).message, 'doesn\'t exist!'); + done(); + } }); }); }); diff --git a/test/fixtures/include-files/expected-data-importer.css b/test/fixtures/include-files/expected-data-importer.css new file mode 100644 index 000000000..1925a6021 --- /dev/null +++ b/test/fixtures/include-files/expected-data-importer.css @@ -0,0 +1,5 @@ +div { + color: yellow; } + +div { + color: yellow; } diff --git a/test/fixtures/include-files/expected-file-importer.css b/test/fixtures/include-files/expected-file-importer.css new file mode 100644 index 000000000..326f694d5 --- /dev/null +++ b/test/fixtures/include-files/expected-file-importer.css @@ -0,0 +1,2 @@ +/* foo.scss */ +/* bar.scss */ diff --git a/test/fixtures/source-map-embed/expected.css b/test/fixtures/source-map-embed/expected.css index 56f2e59a3..a1e895f28 100644 --- a/test/fixtures/source-map-embed/expected.css +++ b/test/fixtures/source-map-embed/expected.css @@ -10,4 +10,4 @@ #navbar li a { font-weight: bold; } -/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAiZml4dHVyZXMvc291cmNlLW1hcC1lbWJlZC9pbmRleC5jc3MiLAoJInNvdXJjZXMiOiBbCgkJImZpeHR1cmVzL3NvdXJjZS1tYXAtZW1iZWQvaW5kZXguc2NzcyIKCV0sCgkibmFtZXMiOiBbXSwKCSJtYXBwaW5ncyI6ICJBQUFBLEFBQUEsT0FBTyxDQUFDO0VBQ04sS0FBSyxFQUFFLEdBQUc7RUFDVixNQUFNLEVBQUUsSUFBSSxHQUNiOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULGVBQWUsRUFBRSxJQUFJLEdBQ3RCOztBQUVELEFBQUEsT0FBTyxDQUFDLEVBQUUsQ0FBQztFQUNULEtBQUssRUFBRSxJQUFJLEdBS1o7RUFORCxBQUdFLE9BSEssQ0FBQyxFQUFFLENBR1IsQ0FBQyxDQUFDO0lBQ0EsV0FBVyxFQUFFLElBQUksR0FDbEIiCn0= */ +/*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJImZpbGUiOiAidGVzdC9maXh0dXJlcy9zb3VyY2UtbWFwLWVtYmVkL2luZGV4LmNzcyIsCgkic291cmNlcyI6IFsKCQkidGVzdC9maXh0dXJlcy9zb3VyY2UtbWFwLWVtYmVkL2luZGV4LnNjc3MiCgldLAoJIm5hbWVzIjogW10sCgkibWFwcGluZ3MiOiAiQUFBQSxBQUFBLE9BQU8sQ0FBQztFQUNOLEtBQUssRUFBRSxHQUFHO0VBQ1YsTUFBTSxFQUFFLElBQUksR0FDYjs7QUFFRCxBQUFBLE9BQU8sQ0FBQyxFQUFFLENBQUM7RUFDVCxlQUFlLEVBQUUsSUFBSSxHQUN0Qjs7QUFFRCxBQUFBLE9BQU8sQ0FBQyxFQUFFLENBQUM7RUFDVCxLQUFLLEVBQUUsSUFBSSxHQUtaO0VBTkQsQUFHRSxPQUhLLENBQUMsRUFBRSxDQUdSLENBQUMsQ0FBQztJQUNBLFdBQVcsRUFBRSxJQUFJLEdBQ2xCIgp9 */ diff --git a/test/lowlevel.js b/test/lowlevel.js index a849a4601..7fd714f05 100644 --- a/test/lowlevel.js +++ b/test/lowlevel.js @@ -29,8 +29,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderSync(options); - assert(/Data context created without a source string/.test(options.result.error), - 'Should fail with error message "Data context created without a source string"'); + assert( + /Data context created without a source string/.test(options.result.error), + 'Should fail with error message "Data context created without a source string"' + ); done(); }); @@ -50,8 +52,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderSync(options); - assert(/Data context created without a source string/.test(options.result.error), - 'Should fail with error message "Data context created without a source string"'); + assert( + /Data context created without a source string/.test(options.result.error), + 'Should fail with error message "Data context created without a source string"' + ); done(); }); @@ -71,8 +75,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderFileSync(options); - assert(/File context created without an input path/.test(options.result.error), - 'Should fail with error message "File context created without an input path"'); + assert( + /File context created without an input path/.test(options.result.error), + 'Should fail with error message "File context created without an input path"' + ); done(); }); @@ -92,8 +98,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderFileSync(options); - assert(/File context created without an input path/.test(options.result.error), - 'Should fail with error message "File context created without an input path"'); + assert( + /File context created without an input path/.test(options.result.error), + 'Should fail with error message "File context created without an input path"' + ); done(); }); @@ -214,8 +222,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderSync(options); - assert(/empty source string/.test(options.result.error), - 'Should fail with error message "Data context created with empty source string"'); + assert( + /empty source string/.test(options.result.error), + 'Should fail with error message "Data context created with empty source string"' + ); done(); }); @@ -235,8 +245,10 @@ describe('lowlevel', function() { result: { stats: {} } }; binding.renderFileSync(options); - assert(/empty input path/.test(options.result.error), - 'Should fail with error message "File context created with empty input path"'); + assert( + /empty input path/.test(options.result.error), + 'Should fail with error message "File context created with empty input path"' + ); done(); }); diff --git a/test/runtime.js b/test/runtime.js index c4ae9d557..35e64dfd8 100644 --- a/test/runtime.js +++ b/test/runtime.js @@ -1,11 +1,11 @@ var assert = require('assert'), sass = process.env.NODESASS_COV - ? require('../lib-cov/extensions') - : require('../lib/extensions'); + ? require('../lib-cov/extensions') + : require('../lib/extensions'); describe('runtime parameters', function() { var pkg = require('../package'), - // Let's use JSON to fake a deep copy + // Let's use JSON to fake a deep copy savedArgv = JSON.stringify(process.argv), savedEnv = JSON.stringify(process.env); diff --git a/test/spec.js b/test/spec.js index 1ccaa8da6..84f8f6274 100644 --- a/test/spec.js +++ b/test/spec.js @@ -4,11 +4,10 @@ var assert = require('assert'), join = require('path').join, read = fs.readFileSync, sass = process.env.NODESASS_COV - ? require('../lib-cov') - : require('../lib'), + ? require('../lib-cov') + : require('../lib'), readYaml = require('read-yaml'), mergeWith = require('lodash/mergeWith'), - assign = require('lodash/assign'), glob = require('glob'), specPath = require('sass-spec').dirname.replace(/\\/g, '/'), impl = 'libsass', @@ -33,7 +32,7 @@ var initialize = function(inputCss, options) { testCase.statusPath = join(folder, 'status'); testCase.optionsPath = join(folder, 'options.yml'); if (exists(testCase.optionsPath)) { - options = mergeWith(assign({}, options), readYaml.sync(testCase.optionsPath), customizer); + options = mergeWith(Object.assign({}, options), readYaml.sync(testCase.optionsPath), customizer); } testCase.includePaths = [ folder, @@ -124,7 +123,7 @@ var executeSuite = function(suite, tests) { var suiteFolderLength = suite.folder.split('/').length; var optionsFile = join(suite.folder, 'options.yml'); if (exists(optionsFile)) { - suite.options = mergeWith(assign({}, suite.options), readYaml.sync(optionsFile), customizer); + suite.options = mergeWith(Object.assign({}, suite.options), readYaml.sync(optionsFile), customizer); } // Push tests in the current suite @@ -154,7 +153,7 @@ var executeSuite = function(suite, tests) { folder: suite.folder + '/' + prevSuite, tests: [], suites: [], - options: assign({}, suite.options), + options: Object.assign({}, suite.options), }, tests.slice(prevSuiteStart, i) ) @@ -170,7 +169,7 @@ var executeSuite = function(suite, tests) { folder: suite.folder + '/' + suiteName, tests: [], suites: [], - options: assign({}, suite.options), + options: Object.assign({}, suite.options), }, tests.slice(prevSuiteStart, tests.length) ) diff --git a/test/watcher.js b/test/watcher.js index c96d39875..742415b1d 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -30,74 +30,37 @@ describe('watcher', function() { it('should record its ancestors as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.changed(file); - assert.deepEqual(files.changed, [ + assert.deepEqual(files, [ path.join(main, 'one.scss'), ]); }); - - it('should record its descendants as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_three.scss'), - ]); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.removed, []); - }); }); describe('if it is not a partial', function() { it('should record itself as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.changed(file); - assert.deepEqual(files.changed, [ + assert.deepEqual(files, [ file, ]); }); - - it('should record its descendants as added', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_one.scss'), - path.join(main, 'partials', '_three.scss'), - ]); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.removed, []); - }); }); }); describe('and is not in the graph', function() { describe('if it is a partial', function() { - it('should not record anything', function() { + it('should record nothing as changed', function() { var file = path.join(sibling, 'partials', '_three.scss'); var files = watcher.changed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); describe('if it is not a partial', function() { - it('should record itself as changed', function() { + it('should record nothing as changed', function() { var file = path.join(sibling, 'three.scss'); var files = watcher.changed(file); - assert.deepEqual(files, { - added: [], - changed: [file], - removed: [], - }); + assert.deepEqual(files, []); }); }); }); @@ -106,59 +69,38 @@ describe('watcher', function() { describe('when a file is added', function() { describe('and it is in the graph', function() { describe('if it is a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, []); - }); - - it('should record its descendants as added', function() { + it('should record its ancestors as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.added(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_three.scss') + assert.deepEqual(files, [ + path.join(main, 'one.scss') ]); }); - - it('should record nothing as changed', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.changed, []); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.removed, []); - }); }); describe('if it is not a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, []); - }); - - it('should record its descendants as added', function() { + it('should record nothing as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.added(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_one.scss'), - path.join(main, 'partials', '_three.scss'), - ]); + assert.deepEqual(files, []); }); + }); + }); + describe('and is not in the graph', function() { + describe('if it is a partial', function() { it('should record nothing as changed', function() { - var file = path.join(main, 'one.scss'); + var file = path.join(sibling, 'partials', '_three.scss'); var files = watcher.added(file); - assert.deepEqual(files.changed, []); + assert.deepEqual(files, []); }); + }); - it('should record nothing as removed', function() { - var file = path.join(main, 'one.scss'); + describe('if it is not a partial', function() { + it('should record nothing as changed', function() { + var file = path.join(sibling, 'three.scss'); var files = watcher.added(file); - assert.deepEqual(files.removed, []); + assert.deepEqual(files, []); }); }); }); @@ -167,44 +109,20 @@ describe('watcher', function() { describe('when a file is removed', function() { describe('and it is in the graph', function() { describe('if it is a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.added, []); - }); - it('should record its ancestors as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.removed(file); - assert.deepEqual(files.changed, [ + assert.deepEqual(files, [ path.join(main, 'one.scss'), ]); }); - - it('should record itself as removed', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.removed, [file]); - }); }); describe('if it is not a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.added, []); - }); - it('should record nothing as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.removed(file); - assert.deepEqual(files.changed, []); - }); - - it('should record itself as removed', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.removed, [file]); + assert.deepEqual(files, []); }); }); }); @@ -214,11 +132,7 @@ describe('watcher', function() { it('should record nothing', function() { var file = path.join(sibling, 'partials', '_three.scss'); var files = watcher.removed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); @@ -226,11 +140,7 @@ describe('watcher', function() { it('should record nothing', function() { var file = path.join(sibling, 'three.scss'); var files = watcher.removed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); }); @@ -248,83 +158,38 @@ describe('watcher', function() { describe('when a file is changed', function() { describe('and it is in the graph', function() { describe('if it is a partial', function() { - it('should record its descendants as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_three.scss'), - ]); - }); - it('should record its ancenstors as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.changed(file); - assert.deepEqual(files.changed, [ + assert.deepEqual(files, [ path.join(main, 'one.scss'), ]); }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.removed, []); - }); }); describe('if it is not a partial', function() { - it('should record its descendants as added', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_one.scss'), - path.join(main, 'partials', '_three.scss'), - ]); - }); - it('should record itself as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.changed(file); - assert.deepEqual(files.changed, [file]); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.removed, []); + assert.deepEqual(files, [file]); }); }); }); describe('and it is not in the graph', function() { describe('if it is a partial', function() { - it('should record nothing', function() { + it('should record nothing as changed', function() { var file = path.join(sibling, 'partials', '_three.scss'); var files = watcher.changed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); describe('if it is not a partial', function() { - it('should record nothing as added', function() { - var file = path.join(sibling, 'three.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.added, []); - }); - it('should record itself as changed', function() { var file = path.join(sibling, 'three.scss'); var files = watcher.changed(file); - assert.deepEqual(files.changed, [file]); - }); - - it('should record nothing as removed', function() { - var file = path.join(sibling, 'three.scss'); - var files = watcher.changed(file); - assert.deepEqual(files.removed, []); + assert.deepEqual(files, []); }); }); }); @@ -332,30 +197,23 @@ describe('watcher', function() { describe('when a file is added', function() { describe('and it is in the graph', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, []); - }); - - it('should record its descendants as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, [ - path.join(main, 'partials', '_three.scss'), - ]); - }); - - it('should record nothing as changed', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.changed, []); + describe('if it is a partial', function() { + it('should record its ancestors as changed', function() { + var file = path.join(main, 'partials', '_one.scss'); + var files = watcher.added(file); + assert.deepEqual(files, [ + path.join(main, 'one.scss'), + ]); + }); }); - it('should record nothing as removed', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.removed, []); + /* testing that this "impossible" situation is handled gracefully */ + describe('if it is not a partial', function() { + it('should record nothing as changed', function() { + var file = path.join(main, 'one.scss'); + var files = watcher.added(file); + assert.deepEqual(files, []); + }); }); }); @@ -368,54 +226,18 @@ describe('watcher', function() { }); describe('if it is a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, [ - file, - ]); - }); - - it('should not record its descendants as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, [ - file, - ]); - }); - it('should record nothing as changed', function() { var file = path.join(main, 'partials', '_three.scss'); var files = watcher.added(file); - assert.deepEqual(files.changed, []); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'partials', '_three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.removed, []); + assert.deepEqual(files, []); }); }); describe('if it is not a partial', function() { - it('should record itself as added', function() { - var file = path.join(main, 'three.scss'); - var files = watcher.added(file); - assert.deepEqual(files.added, [ - file, - ]); - }); - it('should record nothing as changed', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.added(file); - assert.deepEqual(files.changed, []); - }); - - it('should record nothing as removed', function() { - var file = path.join(main, 'one.scss'); + var file = path.join(main, 'three.scss'); var files = watcher.added(file); - assert.deepEqual(files.removed, []); + assert.deepEqual(files, []); }); }); }); @@ -424,44 +246,20 @@ describe('watcher', function() { describe('when a file is removed', function() { describe('and it is in the graph', function() { describe('if it is a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.added, []); - }); - it('should record its ancestors as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.removed(file); - assert.deepEqual(files.changed, [ + assert.deepEqual(files, [ path.join(main, 'one.scss'), ]); }); - - it('should record itself as removed', function() { - var file = path.join(main, 'partials', '_one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.removed, [file]); - }); }); describe('if it is not a partial', function() { - it('should record nothing as added', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.added, []); - }); - it('should record nothing as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.removed(file); - assert.deepEqual(files.changed, []); - }); - - it('should record itself as removed', function() { - var file = path.join(main, 'one.scss'); - var files = watcher.removed(file); - assert.deepEqual(files.removed, [file]); + assert.deepEqual(files, []); }); }); }); @@ -475,26 +273,18 @@ describe('watcher', function() { }); describe('if it is a partial', function() { - it('should record nothing as added', function() { + it('should record nothing as changed', function() { var file = path.join(main, 'partials', '_one.scss'); var files = watcher.removed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); describe('if it is not a partial', function() { - it('should record nothing', function() { + it('should record nothing as changed', function() { var file = path.join(main, 'one.scss'); var files = watcher.removed(file); - assert.deepEqual(files, { - added: [], - changed: [], - removed: [], - }); + assert.deepEqual(files, []); }); }); });