diff --git a/Gruntfile.js b/Gruntfile.js index 2ed5febad2..24a05546b1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -426,11 +426,6 @@ module.exports = function(grunt) { // Load release task grunt.loadTasks('tasks/release'); - // Load typescript task - grunt.registerTask('typescript', function() { - require('./tasks/typescript/task.js')(grunt); - }); - // Load the external libraries used. grunt.loadNpmTasks('grunt-contrib-compress'); grunt.loadNpmTasks('grunt-contrib-connect'); @@ -476,7 +471,7 @@ module.exports = function(grunt) { 'mochaTest' ]); grunt.registerTask('test:nobuild', ['eslint:test', 'connect', 'mocha']); - grunt.registerTask('yui', ['yuidoc:prod', 'minjson', 'typescript']); + grunt.registerTask('yui', ['yuidoc:prod', 'minjson']); grunt.registerTask('yui:test', ['yuidoc:prod', 'connect', 'mocha:yui']); grunt.registerTask('yui:dev', [ 'yui:prod', diff --git a/package.json b/package.json index c5044130b2..38b18777d4 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ }, "license": "LGPL-2.1", "main": "./lib/p5.js", - "types": "./lib/p5.d.ts", "files": [ "license.txt", "lib/p5.min.js", @@ -110,9 +109,7 @@ "lib/addons/p5.sound.js", "lib/addons/p5.sound.min.js", "lib/addons/p5.dom.js", - "lib/addons/p5.dom.min.js", - "lib/p5.d.ts", - "lib/p5.global-mode.d.ts" + "lib/addons/p5.dom.min.js" ], "description": "[![Build Status](https://travis-ci.org/processing/p5.js.svg?branch=master)](https://travis-ci.org/processing/p5.js) [![npm version](https://badge.fury.io/js/p5.svg)](https://www.npmjs.com/package/p5)", "bugs": { diff --git a/tasks/release/release-github.js b/tasks/release/release-github.js index eac59b5ac7..b686a0af20 100644 --- a/tasks/release/release-github.js +++ b/tasks/release/release-github.js @@ -67,12 +67,6 @@ module.exports = function(grunt) { './lib/addons/p5.sound.min.js', 'application/javascript' ], - p5ts: ['p5.d.ts', './lib/p5.d.ts', 'text/plain'], - p5globalts: [ - 'p5.global-mode.d.ts', - './lib/p5.global-mode.d.ts', - 'text/plain' - ], p5zip: ['p5.zip', './p5.zip', 'application/zip'] }; diff --git a/tasks/typescript/emit.js b/tasks/typescript/emit.js deleted file mode 100644 index 861b555a1e..0000000000 --- a/tasks/typescript/emit.js +++ /dev/null @@ -1,119 +0,0 @@ -var fs = require('fs'); -var h2p = require('html2plaintext'); -var wrap = require('word-wrap'); - -function shortenDescription(desc) { - return wrap(h2p(desc).replace(/[\r\n]+/, ''), { - width: 50 - }); -} - -function createEmitter(filename) { - var indentLevel = 0; - var lastText = ''; - var currentSourceFile; - var fd = fs.openSync(filename, 'w'); - - var emit = function(text) { - var indentation = []; - var finalText; - - for (var i = 0; i < indentLevel; i++) { - indentation.push(' '); - } - - finalText = indentation.join('') + text + '\n'; - fs.writeSync(fd, finalText); - - lastText = text; - }; - - emit.description = function(classitem, overload) { - var desc = classitem.description; - if (!desc) { - return; - } - - function emitDescription(desc) { - shortenDescription(desc) - .split('\n') - .forEach(function(line) { - emit(' * ' + line); - }); - } - - emit.sectionBreak(); - emit('/**'); - emitDescription(desc); - emit(' *'); - if (overload) { - var alloverloads = [classitem]; - if (classitem.overloads) { - alloverloads = alloverloads.concat(classitem.overloads); - } - if (overload.params) { - overload.params.forEach(function(p) { - var arg = p.name; - var p2; - for (var i = 0; !p2 && i < alloverloads.length; i++) { - if (alloverloads[i].params) { - p2 = alloverloads[i].params.find( - p3 => p3.description && p3.name === arg - ); - if (p2) { - if (p.optional) { - arg = '[' + arg + ']'; - } - emitDescription('@param ' + arg + ' ' + p2.description); - break; - } - } - } - }); - } - if (overload.chainable) { - emitDescription('@chainable'); - } else if (overload.return && overload.return.description) { - emitDescription('@return ' + overload.return.description); - } - } - emit(' */'); - }; - - emit.setCurrentSourceFile = function(file) { - if (file !== currentSourceFile) { - currentSourceFile = file; - emit.sectionBreak(); - emit('// ' + file); - emit.sectionBreak(); - } - }; - - emit.sectionBreak = function() { - if (lastText !== '' && !/\{$/.test(lastText)) { - emit(''); - } - }; - - emit.getIndentLevel = function() { - return indentLevel; - }; - - emit.indent = function() { - indentLevel++; - }; - - emit.dedent = function() { - indentLevel--; - }; - - emit.close = function() { - fs.closeSync(fd); - }; - - emit('// This file was auto-generated. Please do not edit it.\n'); - - return emit; -} - -module.exports = createEmitter; diff --git a/tasks/typescript/generate-typescript-annotations.js b/tasks/typescript/generate-typescript-annotations.js deleted file mode 100644 index f168061a02..0000000000 --- a/tasks/typescript/generate-typescript-annotations.js +++ /dev/null @@ -1,514 +0,0 @@ -/// @ts-check -const createEmitter = require('./emit'); - -function position(file, line) { - return file + ', line ' + line; -} - -function classitemPosition(classitem) { - return position(classitem.file, classitem.line); -} - -function overloadPosition(classitem, overload) { - return position(classitem.file, overload.line); -} - -// mod is used to make yuidocs "global". It actually just calls generate() -// This design was selected to avoid rewriting the whole file from -// https://github.com/toolness/friendly-error-fellowship/blob/2093aee2acc53f0885fcad252a170e17af19682a/experiments/typescript/generate-typescript-annotations.js -function mod(yuidocs, localFileame, globalFilename, sourcePath) { - var emit; - var constants = {}; - var missingTypes = {}; - - // http://stackoverflow.com/a/2008353/2422398 - var JS_SYMBOL_RE = /^[$A-Z_][0-9A-Z_$]*$/i; - - var P5_CLASS_RE = /^p5\.([^.]+)$/; - - var P5_ALIASES = [ - 'p5', - // These are supposedly "classes" in our docs, but they don't exist - // as objects, and their methods are all defined on p5. - 'p5.dom', - 'p5.sound' - ]; - - var EXTERNAL_TYPES = new Set([ - 'HTMLCanvasElement', - 'HTMLElement', - 'Float32Array', - 'AudioParam', - 'AudioNode', - 'GainNode', - 'DelayNode', - 'ConvolverNode', - 'Event', - 'Blob', - 'null', - 'Node', - 'RegExp', - 'Promise' - ]); - - var YUIDOC_TO_TYPESCRIPT_PARAM_MAP = { - Object: 'object', - Any: 'any', - Number: 'number', - Integer: 'number', - String: 'string', - Constant: 'any', - undefined: 'undefined', - Null: 'null', - Array: 'any[]', - Boolean: 'boolean', - '*': 'any', - Void: 'void', - P5: 'p5', - // When the docs don't specify what kind of function we expect, - // then we need to use the global type `Function` - Function: 'Function', - // Special ignore for hard to fix YUIDoc from p5.sound - 'Tone.Signal': 'any', - SoundObject: 'any' - }; - - function getClassitems(className) { - return yuidocs.classitems.filter(function(classitem) { - // Note that we first find items with the right class name, - // but we also check for classitem.name because - // YUIDoc includes classitems that we want to be undocumented - // just because we used block comments. - // We have other checks in place for finding missing method names - // on public methods so a missing classitem.name implies that - // the method is undocumented on purpose. - // See https://github.com/processing/p5.js/issues/1252 and - // https://github.com/processing/p5.js/pull/2301 - return classitem.class === className && classitem.name; - }); - } - - function isValidP5ClassName(className) { - return ( - (P5_CLASS_RE.test(className) && className in yuidocs.classes) || - (P5_CLASS_RE.test('p5.' + className) && - 'p5.' + className in yuidocs.classes) - ); - } - - /** - * @param {string} type - */ - function validateType(type) { - return translateType(type); - } - - function validateMethod(classitem, overload) { - var errors = []; - var paramNames = {}; - var optionalParamFound = false; - - if (!(JS_SYMBOL_RE.test(classitem.name) || classitem.is_constructor)) { - errors.push('"' + classitem.name + '" is not a valid JS symbol name'); - } - - (overload.params || []).forEach(function(param) { - if (param.optional) { - optionalParamFound = true; - } else if (optionalParamFound) { - errors.push( - 'required param "' + param.name + '" follows an ' + 'optional param' - ); - } - - if (param.name in paramNames) { - errors.push('param "' + param.name + '" is defined multiple times'); - } - paramNames[param.name] = true; - - if (!JS_SYMBOL_RE.test(param.name)) { - errors.push('param "' + param.name + '" is not a valid JS symbol name'); - } - - if (!validateType(param.type)) { - errors.push( - 'param "' + param.name + '" has invalid type: ' + param.type - ); - } - - if (param.type === 'Constant') { - var constantRe = /either\s+(?:[A-Z0-9_]+\s*,?\s*(?:or)?\s*)+/g; - var execResult = constantRe.exec(param.description); - var match; - if (execResult) { - match = execResult[0]; - } - if (classitem.name === 'endShape' && param.name === 'mode') { - match = 'CLOSE'; - } - if (match) { - var values = []; - - var reConst = /[A-Z0-9_]+/g; - var matchConst; - while ((matchConst = reConst.exec(match)) !== null) { - values.push(matchConst); - } - var paramWords = param.name - .split('.') - .pop() - .replace(/([A-Z])/g, ' $1') - .trim() - .toLowerCase() - .split(' '); - var propWords = classitem.name - .split('.') - .pop() - .replace(/([A-Z])/g, ' $1') - .trim() - .toLowerCase() - .split(' '); - - var constName; - if (paramWords.length > 1 || propWords[0] === 'create') { - constName = paramWords.join('_'); - } else if ( - propWords[propWords.length - 1] === - paramWords[paramWords.length - 1] - ) { - constName = propWords.join('_'); - } else { - constName = propWords[0] + '_' + paramWords[paramWords.length - 1]; - } - - constName = constName.toUpperCase(); - constants[constName] = values; - - param.type = constName; - } - } - }); - - if (overload.return && !validateType(overload.return.type)) { - errors.push('return has invalid type: ' + overload.return.type); - } - - return errors; - } - - /** - * - * @param {string} type - * @param {string} [defaultType] - */ - function translateType(type, defaultType) { - if (type === void 0) { - return defaultType; - } - - type = type.trim(); - - if (type === '') { - return ''; - } - - if (type.length > 2 && type.substring(type.length - 2) === '[]') { - return translateType(type.substr(0, type.length - 2), defaultType) + '[]'; - } - - var matchFunction = type.match(/Function\(([^)]*)\)/i); - if (matchFunction) { - var paramTypes = matchFunction[1].split(','); - const mappedParamTypes = paramTypes.map((t, i) => { - const paramName = 'p' + (i + 1); - const paramType = translateType(t, 'any'); - return paramName + ': ' + paramType; - }); - return '(' + mappedParamTypes.join(',') + ') => any'; - } - - var parts = type.split('|'); - if (parts.length > 1) { - return parts.map(t => translateType(t, defaultType)).join('|'); - } - - const staticallyMappedType = YUIDOC_TO_TYPESCRIPT_PARAM_MAP[type]; - if (staticallyMappedType != null) { - return staticallyMappedType; - } - - if (EXTERNAL_TYPES.has(type)) { - return type; - } - - if (isValidP5ClassName(type)) { - return type; - } - - if (constants[type]) { - return type; - } - - missingTypes[type] = true; - return defaultType; - } - - function translateParam(param) { - var name = param.name; - if (name === 'class') { - name = 'theClass'; - } - - return ( - name + - (param.optional ? '?' : '') + - ': ' + - translateType(param.type, 'any') - ); - } - - function generateClassMethod(className, classitem) { - if (classitem.overloads) { - classitem.overloads.forEach(function(overload) { - generateClassMethodWithParams(className, classitem, overload); - }); - } else { - generateClassMethodWithParams(className, classitem, classitem); - } - } - - function generateClassMethodWithParams(className, classitem, overload) { - var errors = validateMethod(classitem, overload); - var params = (overload.params || []).map(translateParam); - var returnType = overload.chainable - ? className - : overload.return ? translateType(overload.return.type, 'any') : 'void'; - var decl; - - if (classitem.is_constructor) { - decl = 'constructor(' + params.join(', ') + ')'; - } else { - decl = - (overload.static ? 'static ' : '') + - classitem.name + - '(' + - params.join(', ') + - '): ' + - returnType; - } - - if (emit.getIndentLevel() === 0) { - decl = 'declare function ' + decl + ';'; - } - - if (errors.length) { - emit.sectionBreak(); - emit( - '// TODO: Fix ' + - classitem.name + - '() errors in ' + - overloadPosition(classitem, overload) + - ':' - ); - emit('//'); - errors.forEach(function(error) { - console.log( - classitem.name + - '() ' + - overloadPosition(classitem, overload) + - ', ' + - error - ); - emit('// ' + error); - }); - emit('//'); - emit('// ' + decl); - emit(''); - } else { - emit.description(classitem, overload); - emit(decl); - } - } - - function generateClassConstructor(className) { - var classitem = yuidocs.classes[className]; - if (classitem.is_constructor) { - generateClassMethod(className, classitem); - } - } - - function generateClassProperty(className, classitem) { - if (JS_SYMBOL_RE.test(classitem.name)) { - // TODO: It seems our properties don't carry any type information, - // which is unfortunate. YUIDocs supports the @type tag on properties, - // and even encourages using it, but we don't seem to use it. - var translatedType = translateType(classitem.type, 'any'); - var defaultValue = classitem.default; - if (classitem.final && translatedType === 'string' && !defaultValue) { - defaultValue = classitem.name.toLowerCase().replace(/_/g, '-'); - } - - var decl; - if (defaultValue) { - decl = classitem.name + ': '; - if (translatedType === 'string') { - decl += "'" + defaultValue.replace(/'/g, "\\'") + "'"; - } else { - decl += defaultValue; - } - } else { - decl = classitem.name + ': ' + translatedType; - } - - emit.description(classitem); - - if (emit.getIndentLevel() === 0) { - const declarationType = classitem.final ? 'const ' : 'var '; - emit('declare ' + declarationType + decl + ';'); - } else { - const modifier = classitem.final ? 'readonly ' : ''; - emit(modifier + decl); - } - } else { - emit.sectionBreak(); - emit( - '// TODO: Property "' + - classitem.name + - '", defined in ' + - classitemPosition(classitem) + - ', is not a valid JS symbol name' - ); - emit.sectionBreak(); - } - } - - function generateClassProperties(className) { - getClassitems(className).forEach(function(classitem) { - classitem.file = classitem.file.replace(/\\/g, '/'); - emit.setCurrentSourceFile(classitem.file); - if (classitem.itemtype === 'method') { - generateClassMethod(className, classitem); - } else if (classitem.itemtype === 'property') { - generateClassProperty(className, classitem); - } else { - emit( - '// TODO: Annotate ' + - classitem.itemtype + - ' "' + - classitem.name + - '", defined in ' + - classitemPosition(classitem) - ); - } - }); - } - - function generateP5Properties(className) { - emit.sectionBreak(); - emit('// Properties from ' + className); - emit.sectionBreak(); - - generateClassConstructor(className); - generateClassProperties(className); - } - - function generateP5Subclass(className) { - var info = yuidocs.classes[className]; - var nestedClassName = className.match(P5_CLASS_RE)[1]; - - info.file = info.file.replace(/\\/g, '/'); - emit.setCurrentSourceFile(info.file); - - emit( - 'class ' + - nestedClassName + - (info.extends ? ' extends ' + info.extends : '') + - ' {' - ); - emit.indent(); - - generateClassConstructor(className); - generateClassProperties(className); - - emit.dedent(); - emit('}'); - } - - function emitConstants() { - emit('// Constants '); - Object.keys(constants).forEach(function(key) { - var values = constants[key]; - - emit('type ' + key + ' ='); - values.forEach(function(v, i) { - var str = ' typeof ' + v; - str = (i ? '|' : ' ') + str; - if (i === values.length - 1) { - str += ';'; - } - emit(' ' + str); - }); - - emit(''); - }); - } - - function generate() { - var p5Aliases = []; - var p5Subclasses = []; - - Object.keys(yuidocs.classes).forEach(function(className) { - if (P5_ALIASES.indexOf(className) !== -1) { - p5Aliases.push(className); - } else if (P5_CLASS_RE.test(className)) { - p5Subclasses.push(className); - } else { - throw new Error( - className + - ' is documented as a class but ' + - "I'm not sure how to generate a type definition for it" - ); - } - }); - - emit = createEmitter(localFileame); - - emit('export = p5;'); - - emit('declare class p5 {'); - emit.indent(); - - p5Aliases.forEach(generateP5Properties); - - emit.dedent(); - emit('}\n'); - - emit('declare namespace p5 {'); - emit.indent(); - - p5Subclasses.forEach(generateP5Subclass); - - emit.dedent(); - emit('}\n'); - - emit.close(); - - emit = createEmitter(globalFilename); - - emit('///\n'); - - p5Aliases.forEach(generateP5Properties); - - emitConstants(); - - emit.close(); - - for (var t in missingTypes) { - console.log('MISSING: ', t); - } - } - - generate(); -} - -module.exports = mod; diff --git a/tasks/typescript/task.js b/tasks/typescript/task.js deleted file mode 100644 index f5ef564a71..0000000000 --- a/tasks/typescript/task.js +++ /dev/null @@ -1,12 +0,0 @@ -var generate = require('./generate-typescript-annotations'); -var path = require('path'); - -module.exports = function(grunt) { - var yuidocs = require('../../docs/reference/data.json'); - var base = path.join(__dirname, '../../lib'); - generate( - yuidocs, - path.join(base, 'p5.d.ts'), - path.join(base, 'p5.global-mode.d.ts') - ); -};